TL: DR:
- ตัวรวบรวมคอมไพเลอร์อาจไม่ได้ตั้งค่าให้ค้นหาการเพิ่มประสิทธิภาพนี้ได้อย่างง่ายดายและอาจมีประโยชน์เฉพาะกับฟังก์ชั่นเล็ก ๆ เท่านั้นไม่ใช่ฟังก์ชั่นขนาดใหญ่ระหว่างการโทร
- การสร้างฟังก์ชั่นขนาดใหญ่เป็นการแก้ปัญหาที่ดีกว่าโดยส่วนใหญ่
- อาจมีความหน่วงแฝงเทียบกับปริมาณงานหาก
foo
ไม่ให้บันทึก / กู้คืน RBX
คอมไพเลอร์เป็นชิ้นส่วนเครื่องจักรที่ซับซ้อน พวกเขาไม่ได้ "ฉลาด" เหมือนมนุษย์และอัลกอริธึมที่มีราคาแพงในการค้นหาการเพิ่มประสิทธิภาพที่เป็นไปได้มักจะไม่คุ้มค่ากับเวลาในการรวบรวมเพิ่ม
ผมรายงานนี้เป็นGCC ข้อผิดพลาด 69986 - รหัสขนาดเล็กลงไปได้ด้วย -Os โดยใช้ผลัก / pop สาด / โหลดกลับมาในปี 2016 ; ไม่มีกิจกรรมหรือคำตอบจาก GCC devs : /
มีความเกี่ยวข้องเล็กน้อย: ข้อผิดพลาดของ GCC 70408 - การนำทะเบียนที่สงวนไว้เดิมมาใช้ซ้ำจะให้รหัสขนาดเล็กลงในบางกรณี - ผู้รวบรวมคอมไพเลอร์บอกฉันว่าจะต้องใช้เวลาจำนวนมากในการทำงานเพื่อให้ GCC สามารถเพิ่มประสิทธิภาพได้ จากการfoo(int)
โทรสองครั้งโดยอิงจากสิ่งที่จะทำให้เป้าหมาย asm ง่ายขึ้น
หาก foo
ไม่บันทึก / กู้คืนrbx
ตัวเองจะมีการแลกเปลี่ยนระหว่างปริมาณงาน (จำนวนคำสั่ง) เทียบกับร้านค้า / เวลาแฝงที่โหลดเพิ่มเติมในx
-> ห่วงโซ่การพึ่งพา retval
คอมไพเลอร์มักจะให้ความสำคัญกับความล่าช้าในการรับส่งข้อมูลเช่นการใช้ 2x LEA แทนimul reg, reg, 10
(ความล่าช้า 3 รอบ, 1 / นาฬิกาทรูพุต) เนื่องจากค่าเฉลี่ยของโค้ดส่วนใหญ่น้อยกว่า 4 uops / นาฬิกาในท่อ 4-wide ทั่วไปเช่น Skylake (คำแนะนำเพิ่มเติม / uops ใช้พื้นที่มากขึ้นใน ROB ลดลงไปข้างหน้าไกลออกไปข้างนอกหน้าต่างเดียวกันของการสั่งซื้อสามารถดูแม้ว่าและการดำเนินการเป็นจริงระเบิดด้วยแผงลอยอาจบัญชีสำหรับบาง uops น้อยกว่า 4 / ค่าเฉลี่ยของนาฬิกา)
ถ้าfoo
ทำ push / pop RBX แสดงว่ามีจำนวน latency ไม่มากนัก การมีการกู้คืนเกิดขึ้นก่อนret
แทนที่จะเป็นเพียงหลังจากนั้นอาจไม่เกี่ยวข้องเว้นแต่จะมีการคาดret
คะเนผิดพลาดหรือ I-cache ที่ทำให้เกิดความล่าช้าในการดึงรหัสในที่อยู่ผู้ส่ง
ฟังก์ชั่นที่ไม่สำคัญส่วนใหญ่จะบันทึก / กู้คืน RBX ดังนั้นจึงมักจะไม่ใช่ข้อสันนิษฐานที่ดีว่าการทิ้งตัวแปรไว้ใน RBX จริง ๆ แล้วจะหมายความว่ามันอยู่ในการลงทะเบียนระหว่างการโทร (แม้ว่าการสุ่มเลือกฟังก์ชันการลงทะเบียนที่สงวนไว้ของการโทรอาจเป็นความคิดที่ดีในการลดบางครั้ง)
ดังนั้นใช่push rdi
/ pop rax
จะมีประสิทธิภาพมากขึ้นในการนี้กรณีและนี่น่าจะเป็นการเพิ่มประสิทธิภาพพลาดสำหรับฟังก์ชั่นที่ไม่ใช่ใบเล็ก ๆ ขึ้นอยู่กับสิ่งfoo
ที่ไม่และความสมดุลระหว่างความล่าช้า / ร้านโหลดพิเศษสำหรับx
เทียบกับคำแนะนำเพิ่มเติมในการบันทึก / rbx
เรียกคืนโทร
เป็นไปได้สำหรับเมตาดาต้าสแต็กเพื่อแสดงการเปลี่ยนแปลงของ RSP ที่นี่เช่นเดียวกับที่เคยใช้sub rsp, 8
ในการรั่วไหล / โหลดซ้ำx
ลงในสแต็กสล็อต ( แต่คอมไพเลอร์ไม่ทราบว่าการเพิ่มประสิทธิภาพนี้อย่างใดอย่างหนึ่งของการใช้push
พื้นที่สำรองและเริ่มต้นตัวแปร. สิ่งที่ C / C ++ คอมไพเลอร์สามารถใช้คำแนะนำการผลักดันป๊อปสำหรับการสร้างตัวแปรท้องถิ่นแทนเพียงเพิ่มขึ้น ESP ครั้ง? . และการทำที่นานกว่า หนึ่ง var ท้องถิ่นจะนำไปสู่.eh_frame
สแต็คขนาดใหญ่คลายเมตาดาต้าเนื่องจากคุณกำลังย้ายตัวชี้สแต็กแยกต่างหากกับการกดแต่ละครั้งซึ่งไม่ได้หยุดคอมไพเลอร์จากการใช้ push / pop เพื่อบันทึก / เรียกคืน regs ที่สงวนไว้สำหรับการโทร)
IDK ถ้ามันคุ้มค่าที่จะสอนคอมไพเลอร์ให้มองหาการปรับให้เหมาะสมนี้
อาจเป็นความคิดที่ดีเกี่ยวกับฟังก์ชั่นทั้งหมดไม่ใช่การโทรหนึ่งครั้งภายในฟังก์ชั่น และอย่างที่ฉันพูดมันขึ้นอยู่กับสมมติฐานในแง่ร้ายที่foo
จะบันทึก / กู้คืน RBX ต่อไป (หรือการปรับให้เหมาะสมสำหรับปริมาณงานถ้าคุณรู้ว่าเวลาแฝงจาก x ถึงคืนค่านั้นไม่สำคัญ แต่คอมไพเลอร์ไม่ทราบและมักจะปรับให้เหมาะกับเวลาแฝง)
หากคุณเริ่มสร้างสมมุติฐานในแง่ร้ายในรหัสจำนวนมาก (เช่นการเรียกใช้ฟังก์ชันเดียวภายในฟังก์ชั่น) คุณจะเริ่มได้รับกรณีเพิ่มเติมที่ RBX ไม่ได้บันทึก / กู้คืนและคุณสามารถใช้ประโยชน์ได้
คุณไม่ต้องการให้การบันทึก / กู้คืน push / pop พิเศษในลูปเพียงบันทึก / กู้คืน RBX ภายนอกลูปและใช้รีจิสเตอร์ที่สงวนการโทรไว้ในลูปที่ทำการเรียกฟังก์ชัน แม้ว่าจะไม่มีลูปก็ตามในกรณีทั่วไปฟังก์ชั่นส่วนใหญ่จะทำการโทรหลายฟังก์ชั่น แนวคิดการปรับให้เหมาะสมนี้สามารถนำไปใช้ได้หากคุณไม่ได้ใช้จริงx
ระหว่างการโทรใด ๆ ก่อนหน้าแรกและหลังสุดท้ายมิฉะนั้นคุณมีปัญหาในการคงการจัดแนวสแต็ก 16 ไบต์สำหรับแต่ละรายการcall
หากคุณทำหนึ่งป๊อปหลังจาก โทรก่อนโทรอีกครั้ง
คอมไพเลอร์ไม่เก่งในฟังก์ชั่นเล็ก ๆ โดยทั่วไป แต่มันก็ไม่ได้ยอดเยี่ยมสำหรับซีพียูเช่นกัน การเรียกใช้ฟังก์ชั่นที่ไม่ใช่แบบอินไลน์มีผลกระทบต่อการปรับให้เหมาะสมที่สุดเท่าที่จะทำได้เว้นแต่ว่าคอมไพเลอร์สามารถมองเห็นภายในของผู้ถูกเจาะและทำการตั้งสมมติฐานมากกว่าปกติ การเรียกใช้ฟังก์ชั่นที่ไม่ใช่แบบอินไลน์เป็นอุปสรรคหน่วยความจำโดยนัย: ผู้เรียกต้องคิดว่าฟังก์ชั่นอาจอ่านหรือเขียนข้อมูลใด ๆ ที่สามารถเข้าถึงได้ทั่วโลกดังนั้น vars ดังกล่าวทั้งหมดต้องซิงค์กับเครื่องนามธรรม C (การวิเคราะห์การหลบหนีช่วยให้คนในท้องถิ่นสามารถลงทะเบียนผ่านการโทรได้หากที่อยู่ของพวกเขาไม่ได้หลบเลี่ยงการทำงาน) นอกจากนี้คอมไพเลอร์จะต้องสมมติว่าการลงทะเบียนการโทรที่ถูกปิดบังนั้นถูกปิดกั้นทั้งหมด สิ่งนี้ดูดสำหรับทศนิยมใน x86-64 System V ซึ่งไม่มีการลงทะเบียน XMM ที่สงวนการโทรไว้
ฟังก์ชั่นเล็ก ๆ อย่างbar()
นั้นดีกว่า inline ในโทรของพวกเขา คอมไพล์ด้วย-flto
ดังนั้นสิ่งนี้สามารถเกิดขึ้นได้ข้ามขอบเขตไฟล์ในกรณีส่วนใหญ่ (พอยน์เตอร์ของฟังก์ชันและขอบเขตไลบรารีที่แชร์สามารถเอาชนะสิ่งนี้ได้)
ฉันคิดว่าเหตุผลหนึ่งที่คอมไพเลอร์ไม่ได้ใส่ใจที่จะลองปรับให้เหมาะสมเหล่านี้คือมันจะต้องใช้รหัสทั้งหมดที่แตกต่างกันในการรวบรวมภายในแตกต่างจากปกติกองซ้อนกับรหัสลงทะเบียนจัดสรรที่รู้วิธีการบันทึกการโทร ลงทะเบียนและใช้พวกเขา
เช่นมันจะเป็นงานที่ต้องใช้จำนวนมากและมีโค้ดจำนวนมากที่ต้องดูแลและถ้ามันกระตือรือร้นที่จะทำสิ่งนี้มันอาจทำให้โค้ดแย่ลง
และก็ว่า (หวังว่า) จะไม่สำคัญ ถ้ามันเป็นเรื่องสำคัญที่คุณควรจะ inlining bar
เข้าโทรหรือ inlining เข้าfoo
bar
นี่เป็นสิ่งที่ดีเว้นแต่จะมีbar
ฟังก์ชั่นที่แตกต่างกันมากมายและfoo
มีขนาดใหญ่และด้วยเหตุผลบางอย่างที่ทำให้พวกเขาไม่สามารถอินไลน์เข้าสู่ผู้โทร