ผลลัพธ์เล็ก ๆ ที่คาดเดาไม่ได้ในการรันโมเดลที่กำหนดไว้


10

ฉันมีโมเดลขนาดใหญ่ (~ 5,000 บรรทัด) เขียนด้วย C มันเป็นโปรแกรมอนุกรมที่ไม่มีการสร้างหมายเลขสุ่มใด ๆ มันใช้ไลบรารี FFTW สำหรับฟังก์ชั่นที่ใช้ FFT - ฉันไม่ทราบรายละเอียดของการใช้งาน FFTW แต่ฉันคิดว่าฟังก์ชั่นในนั้นนั้นถูกกำหนดไว้ด้วย (แก้ไขฉันถ้าฉันผิด)

ปัญหาที่ฉันไม่เข้าใจคือฉันได้รับความแตกต่างเล็กน้อยในผลลัพธ์สำหรับการทำงานที่เหมือนกันในเครื่องเดียวกัน (คอมไพเลอร์เดียวกันไลบรารีเดียวกัน)

ฉันใช้ตัวแปรความแม่นยำสองเท่าและเพื่อผลลัพธ์ผลลัพธ์ในตัวแปรvalueเช่นฉันออก: fprintf(outFID, "%.15e\n", value);หรือ
fwrite(&value, 1, sizeof(double), outFID);

และฉันจะได้รับความแตกต่างอย่างต่อเนื่องเช่น:
2.07843469652206 4 e-16 กับ 2.07843469652206 3 e-16

ฉันใช้เวลามากในการพยายามหาสาเหตุว่าทำไม ตอนแรกฉันคิดว่าชิปหน่วยความจำตัวหนึ่งของฉันเสียไปแล้วและฉันก็สั่งและเปลี่ยนมันให้เป็นประโยชน์ ต่อมาฉันก็ลองรันโค้ดของฉันบนเครื่อง Linux ของเพื่อนร่วมงานและฉันก็มีความแตกต่างในลักษณะเดียวกัน

สิ่งใดที่ทำให้เกิดสิ่งนี้ มันเป็นปัญหาเล็ก ๆ ในตอนนี้ แต่ฉันสงสัยว่ามันเป็น "เคล็ดลับของภูเขาน้ำแข็ง" (ของปัญหาร้ายแรง)

ฉันคิดว่าฉันจะโพสต์ที่นี่แทน StackOverflow ในกรณีที่มีคนทำงานกับแบบจำลองตัวเลขอาจเจอปัญหานี้ หากใครสามารถทำให้กระจ่างเกี่ยวกับเรื่องนี้ฉันจะต้องรับผิดชอบมาก

ติดตามความคิดเห็น:
Christian Clason และ Vikram: ก่อนอื่นขอขอบคุณสำหรับความสนใจในคำถามของฉัน บทความที่คุณเชื่อมโยงเพื่อแนะนำว่า: 1. ข้อผิดพลาดในการปัดเศษจำกัดความถูกต้องและ 2. รหัสอื่น (เช่นการแนะนำคำสั่งการพิมพ์ที่ไม่เป็นอันตราย) อาจส่งผลต่อผลลัพธ์ถึงเครื่อง epsilon ฉันควรชี้แจงว่าฉันไม่ได้เปรียบเทียบผลกระทบfwriteและfprintfฟังก์ชั่น ฉันใช้อันใดอันหนึ่ง โดยเฉพาะอย่างยิ่งปฏิบัติการที่เหมือนกันนั้นถูกใช้สำหรับการรันทั้งคู่ ฉันเพียงแค่การระบุปัญหาเกิดขึ้นไม่ว่าจะเป็นผมใช้หรือfprintffwrite

ดังนั้นเส้นทางรหัส (และปฏิบัติการ) เหมือนกันและฮาร์ดแวร์เหมือนกัน ด้วยปัจจัยภายนอกทั้งหมดเหล่านี้มีค่าคงที่แบบแผนมาจากไหน? ฉันสงสัยว่าการพลิกบิตเกิดขึ้นเนื่องจากหน่วยความจำผิดพลาดที่ไม่ได้รับการเก็บรักษาอย่างถูกต้องซึ่งเป็นเหตุผลที่ฉันเปลี่ยนชิปหน่วยความจำ แต่ดูเหมือนว่าจะไม่เป็นปัญหาที่นี่ฉันยืนยันและคุณระบุ โปรแกรมของฉันแสดงผลตัวเลขความแม่นยำสองเท่านับพันในการเรียกใช้ครั้งเดียวและมีสุ่มจำนวนหนึ่งที่สุ่มบิตพลิก

21016

การติดตาม # 2 :
นี่เป็นโครงเรื่องของอนุกรมเวลาเอาท์พุทโดยรูปแบบเพื่อช่วยในการอภิปราย offshoot ในความคิดเห็น ป้อนคำอธิบายรูปภาพที่นี่


21016

คุณกำลังถามว่าทำไมเครื่องของคุณถึงไม่แม่นยำกว่าความแม่นยำของเครื่อง en.wikipedia.org/wiki/Machine_epsilon
Vikram

1
ดูinf.ethz.ch/personal/gander/Heisenberg/paper.htmlสำหรับตัวอย่างที่เกี่ยวข้องกับอิทธิพลที่ลึกซึ้งของเส้นทางของรหัสบนเลขคณิตจุดลอยตัว และแน่นอนece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/…
Christian Clason

1
1016

2
1

คำตอบ:


9

มีแง่มุมต่าง ๆ ของระบบคอมพิวเตอร์ที่ทันสมัยซึ่งโดยปกติจะไม่สามารถกำหนดได้ซึ่งอาจทำให้เกิดความแตกต่างเหล่านี้ ตราบใดที่ความแตกต่างมีน้อยมากเมื่อเทียบกับความถูกต้องที่จำเป็นสำหรับโซลูชันของคุณอาจไม่มีเหตุผลใด ๆ ที่จะต้องกังวลเกี่ยวกับเรื่องนี้

ตัวอย่างของสิ่งที่ผิดไปจากประสบการณ์ของฉัน พิจารณาปัญหาของการคำนวณผลคูณดอทของเวกเตอร์สองตัว x และ y

d=i=1nxiyi

xiyi

ตัวอย่างเช่นคุณอาจคำนวณผลคูณของเวกเตอร์สองค่าแรกเป็น

d=((x1y1)+(x2y2))+(x3y3)

แล้วเป็น

d=(x1y1)+((x2y2)+(x3y3))

สิ่งนี้จะเกิดขึ้นได้อย่างไร? นี่คือความเป็นไปได้สองอย่าง

  1. การคำนวณแบบมัลติเธรดบนแกนคู่ขนาน คอมพิวเตอร์สมัยใหม่มักจะมีแกนประมวลผล 2, 4, 8 หรือมากกว่านั้นที่สามารถทำงานพร้อมกันได้ หากรหัสของคุณใช้เธรดแบบขนานเพื่อคำนวณผลิตภัณฑ์ดอทบนโปรเซสเซอร์หลายตัวดังนั้นการก่อกวนแบบสุ่มของระบบ (เช่นผู้ใช้ย้ายเมาส์ของเขาและแกนประมวลผลตัวใดตัวหนึ่งต้องประมวลผลการเคลื่อนไหวของเมาส์ก่อนจะกลับสู่ผลิตภัณฑ์ดอท) ส่งผลให้เกิดการเปลี่ยนแปลงในลำดับของการเพิ่ม

  2. การจัดตำแหน่งของข้อมูลและคำแนะนำเวกเตอร์ โปรเซสเซอร์ Intel รุ่นใหม่มีชุดคำสั่งพิเศษที่สามารถใช้งานได้ (ตัวอย่าง) สำหรับเลขทศนิยมในแต่ละครั้ง คำแนะนำเวกเตอร์เหล่านี้ทำงานได้ดีที่สุดหากข้อมูลถูกจัดตำแหน่งในขอบเขต 16 ไบต์ โดยทั่วไปแล้วลูปผลิตภัณฑ์ dot จะแบ่งข้อมูลออกเป็นส่วน ๆ ของ 16 ไบต์ (4 ครั้งต่อครั้ง) หากคุณรันรหัสอีกครั้งในครั้งที่สองข้อมูลอาจถูกจัดตำแหน่งแตกต่างกันกับบล็อกหน่วยความจำ 16 ไบต์เพื่อเพิ่ม ดำเนินการในลำดับที่ต่างกันทำให้ได้คำตอบที่ต่างออกไป

คุณสามารถระบุจุด 1 ได้โดยทำให้โค้ดของคุณทำงานเป็นเธรดเดี่ยวและปิดการประมวลผลแบบขนานทั้งหมด คุณสามารถระบุจุดที่ 2 ได้โดยกำหนดให้การจัดสรรหน่วยความจำจัดเรียงบล็อกของหน่วยความจำ (โดยทั่วไปคุณต้องทำเช่นนี้โดยการรวบรวมรหัสด้วยสวิตช์เช่น -align) หากรหัสของคุณยังให้ผลลัพธ์ที่แตกต่างกัน ที่.

เอกสารนี้จาก Intelกล่าวถึงปัญหาที่อาจนำไปสู่ผลลัพธ์ที่ไม่สามารถทำซ้ำได้ด้วยห้องสมุดคณิตศาสตร์ของ Intel Math เอกสารอื่นจาก Intel ที่กล่าวถึงสวิตช์คอมไพเลอร์เพื่อใช้กับคอมไพเลอร์ของ Intel


ฉันเห็นว่าคุณคิดว่าโค้ดของคุณใช้งานเธรดเดียว แม้ว่าคุณอาจรู้รหัสของคุณดี แต่ฉันก็ไม่แปลกใจถ้าคุณเรียกรูทีนย่อย (เช่นรูทีน BLAS) ที่รันมัลติเธรด คุณควรตรวจสอบเพื่อดูว่าคุณใช้ห้องสมุดอะไรอยู่ คุณยังสามารถใช้เครื่องมือตรวจสอบระบบเพื่อดูการใช้งาน CPU ของคุณ
Brian Borchers

1
หรือตามที่ระบุไว้ในห้องสมุด FFTW ...
คริสเตียน Clason

@BrianBorchers ขอบคุณ ตัวอย่างของการสุ่มที่มาจากธรรมชาติที่ไม่เชื่อมโยงของการเติมจุดลอยตัวคือการรู้แจ้ง Christian Clason นำเสนอประเด็นที่สองเกี่ยวกับว่าผลลัพธ์ของแบบจำลองของฉันมีความหมายหรือไม่เนื่องจากขนาดของตัวเลข - มันอาจเป็นปัญหาใหญ่หากเขาพูดถูก (และฉันเข้าใจเขาอย่างถูกต้อง) ดังนั้นตอนนี้
boxofchalk1

2

ไลบรารี FFTW ที่กล่าวถึงอาจทำงานในโหมดที่ไม่ได้กำหนดไว้

หากคุณกำลังใช้โหมด FFTW_MEASURE หรือ FFTW_PATIENT โปรแกรมจะตรวจสอบที่รันไทม์ซึ่งค่าพารามิเตอร์จะทำงานได้เร็วที่สุดจากนั้นจะใช้พารามิเตอร์เหล่านั้นตลอดทั้งโปรแกรม เนื่องจากเวลาที่ใช้ในการทำงานจะผันผวนเล็กน้อยค่าพารามิเตอร์จะแตกต่างกันและผลลัพธ์ของการแปลงฟูริเยร์จะไม่สามารถกำหนดค่าได้ หากคุณต้องการ FFTW ที่กำหนดขึ้นให้ใช้โหมด FFTW_ESTIMATE


1

แม้ว่าจะเป็นความจริงที่ว่าการเปลี่ยนแปลงลำดับการประเมินผลของนิพจน์อาจเกิดขึ้นได้ดีมากเนื่องจากสถานการณ์การประมวลผลแบบมัลติคอร์ / หลายเธรดอย่าลืมว่าอาจมี (แม้ว่าจะเป็นช็อตเป็นเวลานาน) จำปัญหา Pentium FDIV ได้ไหม (ดูhttps://en.wikipedia.org/wiki/Pentium_FDIV_bug ) เมื่อไม่นานมานี้ฉันได้ทำงานกับซอฟท์แวร์จำลองวงจรแบบแอนะล็อกโดยใช้พีซี ส่วนหนึ่งของวิธีการของเรานั้นเกี่ยวข้องกับการพัฒนาชุดทดสอบการถดถอยซึ่งเราจะทำงานกับการสร้างซอฟต์แวร์ทุกคืน ด้วยโมเดลที่เราพัฒนาขึ้นวิธีการวนซ้ำ (เช่น Newton-Raphson ( https://en.wikipedia.org/wiki/Newton%27s_method)) และ Runge-Kutta) ถูกใช้อย่างกว้างขวางในอัลกอริธึมการจำลอง ด้วยอุปกรณ์อะนาล็อกมักเป็นกรณีที่สิ่งประดิษฐ์ภายในเช่นแรงดันไฟฟ้ากระแส ฯลฯ เกิดขึ้นโดยมีค่าตัวเลขน้อยมาก ค่าเหล่านี้ซึ่งเป็นส่วนหนึ่งของกระบวนการจำลองมีการเปลี่ยนแปลงเพิ่มขึ้นในช่วงเวลา (จำลอง) ขนาดของการเปลี่ยนแปลงเหล่านี้อาจมีขนาดเล็กมากและสิ่งที่เรามักสังเกตเห็นคือการดำเนินการ FPU ที่ตามมาเกี่ยวกับค่าเดลต้าดังกล่าวซึ่งอยู่บนเกณฑ์ "สัญญาณรบกวน" ของความแม่นยำของ FPU (64 บิตลอยตัวมี 53 บิต mantissa, IIRC) ประกอบกับความจริงที่ว่าเรามักจะแนะนำรหัสการบันทึก "PrintF" เป็นแบบจำลองเพื่อให้การดีบั๊ก (อาวันดี ol '!) รับประกันผลการประปรายเป็นประจำทุกวัน! ดังนั้นอะไร ทั้งหมดนี้หมายถึงอะไร คุณต้องคาดหวังว่าจะเห็นความแตกต่างภายใต้สถานการณ์เช่นนี้และสิ่งที่ดีที่สุดที่ต้องทำคือกำหนดและใช้วิธีการตัดสินใจ (ขนาด, ความถี่, แนวโน้ม ฯลฯ ) เมื่อ / วิธีที่จะเพิกเฉย


ขอบคุณ Jim สำหรับข้อมูลเชิงลึก ความคิดใด ๆ เกี่ยวกับปรากฏการณ์พื้นฐานที่จะทำให้ "สิ่งประดิษฐ์ภายใน" เช่นนั้น? ฉันคิดว่าการรบกวนทางแม่เหล็กไฟฟ้าอาจจะเป็นหนึ่ง แต่จากนั้นบิตที่สำคัญจะได้รับผลกระทบด้วยเช่นกัน
boxofchalk1

1

ในขณะที่จุดลอยที่ปัดเศษจากการดำเนินการแบบอะซิงก์อาจเป็นปัญหา แต่ฉันสงสัยว่ามันเป็นสิ่งที่น่าเบื่อมากกว่า การใช้ตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นซึ่งเป็นการเพิ่มการสุ่มให้รหัสที่กำหนดไว้ของคุณ มันเป็นปัญหาทั่วไปที่นักพัฒนามักถูกมองข้ามเพราะเมื่อคุณรันในโหมดดีบั๊กตัวแปรทั้งหมดจะถูกกำหนดค่าเริ่มต้นเป็น 0 เมื่อมีการประกาศ เมื่อไม่ได้ทำงานในโหมดดีบักหน่วยความจำที่กำหนดให้กับตัวแปรมีค่าใด ๆ ที่หน่วยความจำมีก่อนการกำหนด หน่วยความจำไม่ได้เป็นศูนย์ในการกำหนดให้เป็นการเพิ่มประสิทธิภาพ หากสิ่งนี้เกิดขึ้นในรหัสของคุณมันจะง่ายต่อการแก้ไขน้อยกว่าในรหัสไลบรารี

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.