ระดับการปรับให้เหมาะสม -O3 เป็นอันตรายใน g ++ หรือไม่


232

ฉันเคยได้ยินจากแหล่งต่าง ๆ (แม้ว่าส่วนใหญ่มาจากเพื่อนร่วมงานของฉัน) การรวบรวมด้วยระดับการเพิ่มประสิทธิภาพของ-O3ใน g ++ นั้นค่อนข้างอันตรายและควรหลีกเลี่ยงโดยทั่วไปเว้นแต่จะพิสูจน์แล้วว่าจำเป็น

นี่เป็นเรื่องจริงหรือไม่และถ้าเป็นเช่นนั้นทำไม ฉันควรจะติดกับ-O2?


38
มันอันตรายถ้าคุณพึ่งพาพฤติกรรมที่ไม่ได้กำหนด และถึงอย่างนั้นฉันก็ประหลาดใจถ้ามันเป็นระดับการเพิ่มประสิทธิภาพที่ทำให้บางอย่างสับสน
Seth Carnegie

5
คอมไพเลอร์ยังคงถูก จำกัด ให้สร้างโปรแกรมที่ทำงาน "ราวกับว่า" คอมไพล์โค้ดของคุณอย่างแน่นอน ฉันไม่ทราบว่า-O3มีการพิจารณาโดยเฉพาะอย่างยิ่งรถ? ฉันคิดว่าบางทีมันอาจทำให้พฤติกรรมที่ไม่ได้กำหนด "แย่ลง" เพราะอาจทำสิ่งที่แปลกและน่าอัศจรรย์ตามสมมติฐานบางอย่าง แต่นั่นอาจเป็นความผิดของคุณเอง โดยทั่วไปฉันจะบอกว่ามันดี
BoBTFish

5
เป็นความจริงที่ระดับการเพิ่มประสิทธิภาพที่สูงขึ้นมีแนวโน้มที่จะมีข้อบกพร่องของคอมไพเลอร์ ฉันได้ตีบางกรณีด้วยตัวเอง แต่โดยทั่วไปแล้วพวกเขาก็ยังหายากอยู่ดี
ลึกลับ

21
-O2เปิดใช้-fstrict-aliasingงานและหากรหัสของคุณยังมีชีวิตอยู่รหัสดังกล่าวอาจจะอยู่รอดจากการเพิ่มประสิทธิภาพอื่น ๆ เนื่องจากเป็นสิ่งที่ผู้คนผิดพลาดซ้ำแล้วซ้ำอีก ที่กล่าวว่า-fpredictive-commoningมีเฉพาะใน-O3และการเปิดใช้งานที่อาจเปิดใช้งานข้อบกพร่องในรหัสของคุณเกิดจากสมมติฐานที่ไม่ถูกต้องเกี่ยวกับการเกิดพร้อมกัน ผิดน้อยลงรหัสของคุณคือการเพิ่มประสิทธิภาพอันตรายน้อยเป็น ;-)
สตีฟเจสซอพ

6
@PlasmaHH ฉันไม่คิดว่า "stricter" เป็นคำอธิบายที่ดี-Ofastมันปิดการจัดการ NaN ที่สอดคล้องกับมาตรฐาน IEEE
Jonathan Wakely

คำตอบ:


223

ในวันแรกของ gcc (2.8 ฯลฯ ) และในช่วงเวลาของ egcs และ redhat 2.96 -O3 ค่อนข้างบั๊กกี้บางครั้ง แต่นี่เป็นเวลากว่าทศวรรษที่ผ่านมาและ -O3 ไม่แตกต่างจากการเพิ่มประสิทธิภาพระดับอื่น ๆ (ในรถ)

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

ในฐานะที่เป็นบันทึกย่อส่วนตัวฉันกำลังใช้งานซอฟต์แวร์การผลิตในภาคการเงินเป็นเวลาหลายปีในขณะนี้ด้วย -O3 และยังไม่พบข้อผิดพลาดที่จะไม่เกิดขึ้นถ้าฉันจะใช้ -O2

ตามความต้องการที่เป็นที่นิยมเพิ่มที่นี่:

-O3 และโดยเฉพาะอย่างยิ่งการตั้งค่าสถานะเพิ่มเติมเช่น -Funroll-loops (ไม่ได้เปิดใช้งานโดย -O3) บางครั้งสามารถนำไปสู่การสร้างรหัสเครื่องเพิ่มเติม ภายใต้สถานการณ์บางอย่าง (เช่นใน cpu ที่มีแคชคำสั่ง L1 ขนาดเล็กเป็นพิเศษ) สิ่งนี้อาจทำให้เกิดการชะลอตัวเนื่องจากรหัสทั้งหมดเช่นบางวงในตอนนี้ไม่เหมาะสมใน L1I อีกต่อไป โดยทั่วไปแล้ว gcc พยายามอย่างหนักที่จะไม่สร้างรหัสมากนัก แต่เนื่องจากโดยทั่วไปแล้วจะเป็นการเพิ่มประสิทธิภาพของกรณีทั่วไปจึงสามารถเกิดขึ้นได้ ตัวเลือกโดยเฉพาะอย่างยิ่งมีแนวโน้มที่จะนี้ (เช่นการยกเลิกการวนรอบ) มักจะไม่รวมอยู่ใน -O3 และมีการทำเครื่องหมายตามใน manpage ดังนั้นจึงเป็นความคิดที่ดีที่จะใช้ -O3 ในการสร้างรหัสอย่างรวดเร็วและถอยกลับไปที่ -O2 หรือ -Os (ซึ่งพยายามปรับขนาดรหัสให้เหมาะสม) เมื่อเหมาะสม (เช่นเมื่อ profiler ระบุว่า L1I คิดถึง)

หากคุณต้องการเพิ่มประสิทธิภาพสูงสุดคุณสามารถปรับเปลี่ยน gcc ผ่านทาง - เปรียบเทียบค่าใช้จ่ายที่เกี่ยวข้องกับการปรับให้เหมาะสมบางอย่าง นอกจากนี้โปรดทราบว่าตอนนี้ gcc มีความสามารถในการใส่คุณลักษณะที่ฟังก์ชั่นที่ควบคุมการตั้งค่าการเพิ่มประสิทธิภาพเฉพาะสำหรับฟังก์ชั่นเหล่านี้ดังนั้นเมื่อคุณพบว่าคุณมีปัญหากับ -O3 ในหนึ่งฟังก์ชั่น คุณไม่จำเป็นต้องรวบรวมไฟล์ทั้งหมดหรือแม้แต่โครงการทั้งหมดด้วย O2

ดูเหมือนว่าจะต้องใช้ความระมัดระวังเมื่อใช้ -Ofast ซึ่งระบุว่า:

- รวดเร็วเปิดใช้งานการปรับให้เหมาะสมทั้งหมด -O3 นอกจากนี้ยังเปิดใช้งานการปรับให้เหมาะสมที่ไม่ถูกต้องสำหรับโปรแกรมที่เป็นไปตามมาตรฐานทั้งหมด

ซึ่งทำให้ฉันสรุปได้ว่า -O3 มีวัตถุประสงค์เพื่อให้เป็นไปตามมาตรฐานอย่างสมบูรณ์


2
ฉันแค่ใช้สิ่งที่ตรงกันข้าม ฉันมักจะใช้ -Os หรือ -O2 (บางครั้ง O2 สร้างไฟล์ปฏิบัติการขนาดเล็ก) .. หลังจากทำโปรไฟล์ฉันใช้ O3 ในส่วนของรหัสที่ใช้เวลาในการประมวลผลนานกว่า
CoffeDeveloper

3
ฉันทำอย่างนั้นเพื่อความเร็ว O3 ครั้งส่วนใหญ่ทำให้สิ่งต่าง ๆ ช้าลง ไม่รู้ว่าทำไมฉันสงสัยว่ามันก่อให้เกิดมลพิษคำแนะนำแคช
CoffeDeveloper

4
@DarioOO ฉันรู้สึกอยากอ้อนวอน "code bloat" เป็นสิ่งที่ได้รับความนิยม แต่ฉันแทบไม่เคยเห็นมันมาหนุนด้วยการวัดประสิทธิภาพ มันขึ้นอยู่กับสถาปัตยกรรมเป็นจำนวนมาก แต่ทุกครั้งที่ฉันเห็นเกณฑ์มาตรฐานที่ตีพิมพ์ (เช่นphoronix.com/… ) มันแสดงให้เห็นว่า O3 นั้นเร็วขึ้นในกรณีส่วนใหญ่ ฉันได้เห็นการวิเคราะห์การทำโปรไฟล์และระมัดระวังเพื่อพิสูจน์ว่าการขยายตัวของรหัสนั้นเป็นปัญหาจริง ๆ และมักจะเกิดขึ้นเฉพาะกับผู้ที่ยอมรับแม่แบบในแบบสุดขั้วเท่านั้น
Nir Friedman

1
@NirFriedman: มีแนวโน้มที่จะได้รับปัญหาเมื่อโมเดลต้นทุนอินไลน์ของคอมไพเลอร์มีข้อบกพร่องหรือเมื่อคุณปรับให้เหมาะสมสำหรับเป้าหมายที่แตกต่างโดยสิ้นเชิงกว่าที่คุณทำงาน สิ่งนี้นำไปใช้กับการเพิ่มประสิทธิภาพในทุกระดับ ... พ
ลาสม่า

1
@PlasmaHH: ปัญหาการใช้ cmov คงเป็นเรื่องยากที่จะแก้ไขสำหรับกรณีทั่วไป โดยปกติแล้วคุณยังไม่ได้เป็นเพียงแค่เรียงข้อมูลของคุณดังนั้นเมื่อ gcc พยายามที่จะตัดสินใจได้ว่าเป็นสาขาเป็นที่คาดหมายหรือไม่วิเคราะห์คงมองหาการโทรไปยังstd::sortฟังก์ชั่นไม่น่าจะช่วยเหลือ การใช้บางอย่างเช่นstackoverflow.com/questions/109710/…อาจช่วยหรือเขียนแหล่งข้อมูลเพื่อใช้ประโยชน์จากการเรียงลำดับ: สแกนจนกว่าคุณจะเห็น> = 128 จากนั้นเริ่มรวม สำหรับรหัสป่องใช่ฉันตั้งใจจะไปรอบ ๆ เพื่อรายงานมัน : P
Peter Cordes

42

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

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


10
"เกือบตลอดเวลา"? ทำให้เป็น "50-50" และเราจะมีข้อตกลง ;-)
ไม่มีข้อบกพร่องกระต่าย

12

ตัวเลือก -O3 จะเปิดใช้งานการเพิ่มประสิทธิภาพที่มีราคาแพงกว่าเช่นฟังก์ชั่นอินไลน์ซับไลน์นอกเหนือจากการปรับแต่งทั้งหมดของระดับล่าง '-O2' และ '-O1' ระดับการปรับให้เหมาะสม '-O3' อาจเพิ่มความเร็วของการเรียกใช้งานผลลัพธ์ แต่ยังสามารถเพิ่มขนาดได้ ภายใต้สถานการณ์บางอย่างที่การเพิ่มประสิทธิภาพเหล่านี้ไม่ดีตัวเลือกนี้อาจทำให้โปรแกรมช้าลง


3
ฉันเข้าใจว่า "การเพิ่มประสิทธิภาพชัดเจน" บางอย่างอาจทำให้โปรแกรมช้าลง แต่คุณมีแหล่งที่อ้างว่า GCC -O3 ทำให้โปรแกรมช้าลงหรือไม่
Mooing Duck

1
@MooingDuck: ในขณะที่ฉันไม่สามารถอ้างถึงแหล่งที่มาฉันจำได้ว่าทำงานในกรณีเช่นนี้กับโปรเซสเซอร์ AMD รุ่นเก่าบางรุ่นที่มีแคช L1I ค่อนข้างเล็ก (คำแนะนำ ~ 10k) ฉันแน่ใจว่า google มีมากกว่าสำหรับผู้ที่สนใจ แต่ตัวเลือกโดยเฉพาะอย่างยิ่งเช่นการยกเลิกการวนซ้ำไม่ใช่ส่วนหนึ่งของ O3 และขนาดที่เพิ่มขึ้นเหล่านั้นมีจำนวนมาก -Os คือหนึ่งสำหรับเมื่อคุณต้องการที่จะทำให้ปฏิบัติการที่เล็กที่สุด แม้ -O2 สามารถเพิ่มขนาดรหัส เครื่องมือที่ดีในการเล่นกับผลลัพธ์ของระดับการเพิ่มประสิทธิภาพที่แตกต่างกันคือ gcc explorer
PlasmaHH

@PlasmaHH: จริง ๆ แล้วขนาดแคชเล็ก ๆ เป็นสิ่งที่คอมไพเลอร์อาจทำให้พลาด นั่นเป็นตัวอย่างที่ดีจริงๆ กรุณาใส่ไว้ในคำตอบ
Mooing Duck

1
@PlasmaHH Pentium III มีรหัสแคช 16KB AMD K6 และสูงกว่านั้นมีแคชคำสั่ง 32KB P4 เริ่มต้นด้วยค่าประมาณ 96KB Core I7 จริง ๆ แล้วมีรหัสแคช 32KB L1 ตัวถอดรหัสคำสั่งมีความแข็งแรงในทุกวันนี้ L3 ของคุณดีพอที่จะถอยกลับไปได้เกือบทุกลูป
doug65536

1
คุณจะเห็นการเพิ่มขึ้นอย่างมหาศาลทุกครั้งที่มีฟังก์ชั่นที่เรียกว่าในลูปและมันสามารถทำการกำจัด subexpression ที่สำคัญและยกการคำนวณที่ไม่จำเป็นออกจากฟังก์ชั่นก่อนลูป
doug65536

8

ใช่แล้ว O3 เป็นคนบ้ารถ ฉันเป็นนักพัฒนาคอมไพเลอร์และฉันได้ระบุข้อผิดพลาด gcc ที่ชัดเจนและชัดเจนที่เกิดจาก O3 สร้างคำแนะนำการประกอบ SIMD buggy เมื่อสร้างซอฟต์แวร์ของตัวเอง จากสิ่งที่ฉันเห็นซอฟต์แวร์การผลิตส่วนใหญ่มาพร้อมกับ O2 ซึ่งหมายความว่า O3 จะได้รับความสนใจน้อยลงในการทดสอบ wrt และแก้ไขข้อผิดพลาด

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


3

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

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

การ์ดไม่ทำงานตามที่คาดไว้ เมื่อฉันเห็นแอสเซมบลีฉันเข้าใจว่าคอมไพเลอร์เขียนsomeCommand[ the last ]ลงไปpciMemoryเท่านั้นละเว้นการเขียนก่อนหน้าทั้งหมด

โดยสรุป: มีความถูกต้องและใส่ใจกับการเพิ่มประสิทธิภาพ


38
แต่ประเด็นคือโปรแกรมของคุณมีพฤติกรรมที่ไม่ได้กำหนด เครื่องมือเพิ่มประสิทธิภาพไม่ได้ทำอะไรผิด โดยเฉพาะอย่างยิ่งที่คุณจำเป็นต้องประกาศเป็นpciMemory volatile
Konrad Rudolph

11
มันไม่ใช่ UB จริง ๆ แต่คอมไพเลอร์อยู่ในสิทธิ์ที่จะละเว้นทั้งหมด แต่การเขียนครั้งสุดท้ายไปpciMemoryเพราะการเขียนอื่น ๆ ทั้งหมดไม่มีผล สำหรับเครื่องมือเพิ่มประสิทธิภาพที่ยอดเยี่ยมเพราะสามารถลบคำแนะนำที่ไร้ประโยชน์และเสียเวลามากมาย
Konrad Rudolph

4
ฉันพบสิ่งนี้ในมาตรฐาน (หลังจาก 10+ ปี))) - การประกาศแบบระเหยอาจใช้เพื่ออธิบายวัตถุที่สอดคล้องกับพอร์ตอินพุต / เอาท์พุตที่แมปหน่วยความจำหรือวัตถุที่เข้าถึงได้โดยฟังก์ชั่นการขัดจังหวะแบบอะซิงโครนัส การดำเนินการกับวัตถุที่ประกาศดังนั้นจะไม่ถูกปรับให้เหมาะสมโดยการนำไปปฏิบัติหรือจัดลำดับใหม่เว้นแต่จะได้รับอนุญาตจากกฎสำหรับการประเมินนิพจน์
borisbn

2
@borisbn ค่อนข้างปิดหัวข้อ แต่คุณจะรู้ได้อย่างไรว่าอุปกรณ์ของคุณได้รับคำสั่งก่อนที่จะส่งคำสั่งใหม่?
user877329

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