คอมไพเลอร์สร้างโค้ดที่ดีกว่าสำหรับ do-while loops เมื่อเทียบกับลูปประเภทอื่น ๆ หรือไม่?


89

มีความคิดเห็นในไลบรารีการบีบอัด zlib (ซึ่งใช้ในโปรเจ็กต์ Chromium และอื่น ๆ อีกมากมาย) ซึ่งหมายความว่า do-while loop ใน C สร้างโค้ด "ดีกว่า" ในคอมไพเลอร์ส่วนใหญ่ นี่คือข้อมูลโค้ดที่ปรากฏ

https://code.google.com/p/chromium/codesearch#chromium/src/third_party/zlib/deflate.c&l=1225

มีหลักฐานว่าคอมไพเลอร์ส่วนใหญ่ (หรือใด ๆ ) จะสร้างโค้ดที่ดีกว่า (เช่นมีประสิทธิภาพมากกว่า)?

อัปเดต: Mark Adlerหนึ่งในผู้เขียนต้นฉบับได้ให้บริบทเล็กน้อยในความคิดเห็น


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

1
The funny "do {}" generates better code--- ดีกว่ายังไง? ตลกกว่าในขณะที่ () หรือน่าเบื่อกว่าปกติทำ {}?
. 'สรรพนาม' ม.

@ H2CO3 ขอบคุณสำหรับคำชี้แจงฉันได้แก้ไขคำถามให้เจาะจงมากขึ้นเกี่ยวกับที่มา
Dennis

42
ความคิดเห็นนั้นเขียนขึ้นเมื่อ 18 ปีก่อนในยุคของคอมไพเลอร์ Borland และ Sun C ความเกี่ยวข้องใด ๆ กับคอมไพเลอร์ในปัจจุบันน่าจะเป็นเรื่องบังเอิญอย่างแท้จริง โปรดทราบว่าการใช้งานเฉพาะนี้doเมื่อเทียบกับเพียงwhileไม่ได้หลีกเลี่ยงสาขาที่มีเงื่อนไข
Mark Adler

คำตอบ:


108

ก่อนอื่น:

do-whileห่วงไม่ได้เช่นเดียวกับwhile-loop หรือfor-loop

  • whileและforลูปอาจไม่ทำงานในร่างกายของลูปเลย
  • do-whileห่วงเสมอวิ่งร่างกายห่วงอย่างน้อยหนึ่งครั้ง - มันข้ามการตรวจสอบสภาวะเริ่มต้น

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

ดังนั้นเพื่อหลีกเลี่ยงการเปรียบเทียบแอปเปิ้ลกับส้มฉันจะดำเนินการต่อโดยสมมติว่าลูปจะทำงานอย่างน้อยหนึ่งครั้งเสมอ นอกจากนี้ฉันจะไม่พูดถึงforลูปอีกเพราะโดยพื้นฐานแล้วwhileลูปนั้นมีน้ำตาลไวยากรณ์เล็กน้อยสำหรับตัวนับลูป

ดังนั้นฉันจะตอบคำถาม:

หากwhileรับประกันการวนซ้ำอย่างน้อยหนึ่งครั้งจะมีประสิทธิภาพที่เพิ่มขึ้นจากการใช้do-whileลูปแทนหรือไม่


do-whileข้ามการตรวจสอบเงื่อนไขแรก ดังนั้นจึงมีสาขาน้อยกว่าหนึ่งสาขาและมีเงื่อนไขน้อยกว่าที่จะประเมิน

หากเงื่อนไขมีราคาแพงในการตรวจสอบและคุณรู้ว่าคุณรับประกันว่าจะวนซ้ำอย่างน้อยหนึ่งครั้งการdo-whileวนซ้ำอาจเร็วขึ้น

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


กล่าวอีกนัยหนึ่ง while-loop:

มีประสิทธิภาพเช่นเดียวกับสิ่งนี้:

หากคุณรู้ว่าคุณมักจะวนซ้ำอย่างน้อยหนึ่งครั้งคำสั่ง if นั้นไม่เกี่ยวข้อง


เช่นเดียวกันในระดับแอสเซมบลีนี่คือวิธีการที่ลูปต่างๆคอมไพล์เป็น:

ห่วงขณะทำ:

ในขณะที่ห่วง:

โปรดทราบว่าเงื่อนไขซ้ำกัน แนวทางอื่นคือ:

... ซึ่งแลกเปลี่ยนรหัสที่ซ้ำกันเพื่อการกระโดดเพิ่มเติม

ไม่ว่าจะด้วยวิธีใดก็ยังแย่กว่าการdo-whileวนซ้ำปกติ

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


แต่สิ่งที่ค่อนข้างแปลกสำหรับตัวอย่างเฉพาะในคำถามเพราะมีเนื้อหาวนซ้ำว่างเปล่า เนื่องจากมีร่างกายไม่ไม่มีความแตกต่างระหว่างตรรกะและwhiledo-while

FWIW ฉันทดสอบสิ่งนี้ใน Visual Studio 2012:

  • ด้วยร่างกายที่ว่างเปล่าก็ไม่จริงสร้างรหัสเดียวกันและwhile do-whileดังนั้นส่วนนั้นน่าจะเป็นสิ่งที่หลงเหลืออยู่ในสมัยก่อนที่คอมไพเลอร์ยังไม่ดีเท่า

  • แต่ด้วยร่างกายที่ไม่ว่างเปล่า VS2012 สามารถหลีกเลี่ยงการทำซ้ำรหัสเงื่อนไขได้ แต่ก็ยังสร้างการกระโดดตามเงื่อนไขเพิ่มเติม

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

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


12
ดังนั้นการตรวจสอบเงื่อนไขเพียงครั้งเดียวจึงเป็นประโยชน์อย่างมากหรือไม่? ฉันสงสัยอย่างมาก เรียกใช้ลูป 100 ครั้งและไม่มีนัยสำคัญเลย

7
@ H2CO3 แต่ถ้าลูปทำงานเพียงครั้งเดียวหรือสองครั้งล่ะ? แล้วขนาดโค้ดที่เพิ่มขึ้นจากโค้ดเงื่อนไขที่ซ้ำกันล่ะ?
Mysticial

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

30
@ H2CO3 "หากลูปทำงานเพียงครั้งหรือสองครั้งการวนซ้ำนั้นไม่คุ้มค่าที่จะเพิ่มประสิทธิภาพ" - ฉันขอแตกต่าง มันอาจอยู่ในวงอื่น รหัส HPC ที่ได้รับการปรับให้เหมาะสมที่สุดของฉันเองเป็นแบบนี้ และใช่ do-while สร้างความแตกต่าง
Mysticial

29
@ H2CO3 ไหนบอกว่าให้กำลังใจมัน คำถามที่ถามคือการdo-whileวนซ้ำเร็วกว่าการวนซ้ำในขณะที่ และฉันตอบคำถามโดยบอกว่ามันเร็วกว่า ฉันไม่ได้บอกว่าเท่าไหร่ ผมไม่ได้บอกว่าคุ้มไหม ฉันไม่ได้แนะนำให้ใครเริ่มแปลงเป็นลูป do-while แต่การปฏิเสธเพียงว่ามีความเป็นไปได้ในการเพิ่มประสิทธิภาพแม้ว่าจะเป็นเพียงเล็กน้อย แต่ในความคิดของฉันก็เป็นการทำลายผู้ที่ใส่ใจและสนใจสิ่งเหล่านี้
Mysticial

24

มีหลักฐานว่าคอมไพเลอร์ส่วนใหญ่ (หรือใด ๆ ) จะสร้างโค้ดที่ดีกว่า (เช่นมีประสิทธิภาพมากกว่า)?

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

นี่อาจเป็นเรื่องที่น่ากังวลเมื่อหลายสิบปีก่อน (เมื่อเขียน ZLib แล้ว) แต่ไม่แน่นอนในปัจจุบันเว้นแต่คุณจะพบโดยการทำโปรไฟล์จริงสิ่งนี้จะลบปัญหาคอขวดออกจากโค้ดของคุณ


9
ใส่ดี - วลีpremature optimizationอยู่ในใจที่นี่
James Snell

@JamesSnell เป๊ะ. และนั่นคือสิ่งที่คำตอบยอดนิยมสนับสนุน / ให้กำลังใจ

16
ฉันไม่คิดว่าคำตอบที่ได้รับคะแนนสูงสุดจะสนับสนุนการเพิ่มประสิทธิภาพก่อนกำหนด ฉันจะยืนยันว่ามันแสดงให้เห็นถึงความแตกต่างในประสิทธิภาพที่เป็นไปได้ไม่ว่าจะเล็กน้อยหรือไม่มีนัยสำคัญ แต่ผู้คนตีความสิ่งต่างออกไปและบางคนอาจมองว่าเป็นสัญญาณให้เริ่มใช้ do-while loops เมื่อไม่จำเป็น (ฉันหวังว่าจะไม่) อย่างไรก็ตามฉันดีใจกับคำตอบทั้งหมดที่ผ่านมา พวกเขาให้ข้อมูลที่มีค่าเขียนคำถามและสร้างการสนทนาที่น่าสนใจ
Dennis

16

โดยสรุป (tl; dr):

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


รายละเอียด:

เป็นการยากที่จะบอกว่าผู้เขียนต้นฉบับหมายถึงอะไรจากความคิดเห็นของเขาเกี่ยวกับdo {} whileการสร้างโค้ดที่ดีกว่านี้ แต่ฉันต้องการคาดเดาในทิศทางอื่นที่ไม่ใช่สิ่งที่ยกมาที่นี่ - เราเชื่อว่าความแตกต่างระหว่างdo {} whileและwhile {}ลูปนั้นค่อนข้างบาง (หนึ่งสาขาน้อยกว่า Mystical กล่าว) แต่มีบางอย่างที่ "สนุกกว่า" ในรหัสนี้และนั่นทำให้งานทั้งหมดอยู่ในสภาพที่บ้าคลั่งนี้และทำให้ส่วนภายในว่างเปล่า ( do {})

ฉันได้ลองใช้รหัสต่อไปนี้บน gcc 4.8.1 (-O3) และให้ความแตกต่างที่น่าสนใจ -

หลังจากรวบรวม -

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


ในทางกลับกันเสียงดัง 3.3 (-O3) ทั้งสองลูปจะสร้างรหัสคำสั่ง 5 ข้อนี้:

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


บรรทัดล่างสุด - หากคุณต้องการปรับให้เหมาะสมกับโค้ดที่ดีที่สุดเท่าที่จะเป็นไปได้ (และคุณรู้ว่าควรมีลักษณะอย่างไร) ให้ทำการประกอบโดยตรงและตัด "คนกลาง" (คอมไพเลอร์) ออกจากสมการ แต่ให้คำนึงถึงสิ่งที่ใหม่กว่า คอมไพเลอร์และ HW ที่ใหม่กว่าอาจทำให้การเพิ่มประสิทธิภาพนี้ล้าสมัย ในกรณีส่วนใหญ่ควรปล่อยให้คอมไพเลอร์ทำงานในระดับนั้นให้คุณและมุ่งเน้นไปที่การเพิ่มประสิทธิภาพสิ่งสำคัญ ๆ

อีกจุดหนึ่งที่ควรทำ - การนับคำสั่ง (สมมติว่านี่คือสิ่งที่รหัสของ OP ดั้งเดิมตามมา) ไม่ได้เป็นการวัดประสิทธิภาพของโค้ดที่ดี คำสั่งทั้งหมดไม่ได้ถูกสร้างขึ้นเท่ากันและบางคำสั่ง (การย้าย reg-to-reg อย่างง่ายสำหรับเช่น) นั้นมีราคาถูกมากเนื่องจากได้รับการปรับให้เหมาะสมโดย CPU การเพิ่มประสิทธิภาพอื่น ๆ อาจส่งผลกระทบต่อการเพิ่มประสิทธิภาพภายในของ CPU ดังนั้นในที่สุดก็จะนับเฉพาะการเปรียบเทียบที่เหมาะสมเท่านั้น


ดูเหมือนว่าจะบันทึกการย้ายทะเบียน mov %rcx,%rsi:) ฉันเห็นว่าการจัดเรียงโค้ดใหม่สามารถทำได้อย่างไร
อาถรรพ์

@Mystical คุณพูดถูกเกี่ยวกับการเพิ่มประสิทธิภาพไมโคร บางครั้งแม้แต่การบันทึกคำสั่งเดียวก็ไม่คุ้มค่าอะไรเลย (และการย้าย reg-to-reg ก็แทบจะฟรีเมื่อเปลี่ยนชื่อ reg ในวันนี้)
Leeor

ดูเหมือนว่าจะไม่มีการเปลี่ยนชื่อการย้ายจนกระทั่ง AMD Bulldozer และ Intel Ivy Bridge ที่น่าแปลกใจ!
Mysticial

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

3
ดูเหมือนว่าคุณตีความความคิดเห็นในโค้ดต้นฉบับแตกต่างจากส่วนใหญ่และเป็นเรื่องที่สมเหตุสมผล ความคิดเห็นระบุว่า "ตลกดี {} .. " แต่ไม่ได้บอกว่าเปรียบเทียบกับเวอร์ชันที่ไม่ตลก คนส่วนใหญ่รู้ถึงความแตกต่างระหว่าง do-while และ while ดังนั้นการคาดเดาของฉันก็คือ "the funny do {}" ไม่ได้ใช้กับสิ่งนั้น แต่ใช้กับการคลายการวนซ้ำและ / หรือการขาดงานพิเศษตามที่คุณแสดง ที่นี่.
Abel

10

whileวงมักจะถูกรวบรวมเป็นdo-whileห่วงที่มีสาขาเริ่มต้นไปอยู่ในสภาพเช่น

ในขณะที่การรวบรวมdo-whileลูปจะเหมือนกันโดยไม่มีสาขาเริ่มต้น คุณจะเห็นได้จากwhile()ค่าใช้จ่ายของสาขาเริ่มต้นที่มีประสิทธิภาพน้อยกว่าซึ่งจ่ายเพียงครั้งเดียว [เปรียบเทียบกับวิธีการใช้งานแบบไร้เดียงสาwhile,ซึ่งต้องใช้ทั้งสาขาเงื่อนไขและสาขาที่ไม่มีเงื่อนไขต่อการวนซ้ำ]

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


7

ข้อสังเกตไม่เกี่ยวกับการเลือกคำสั่งควบคุม (do vs. while) มันเกี่ยวกับการคลายลูป !!!

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

การใช้งานครั้งหลังนี้ทำได้เร็วขึ้นอย่างแน่นอนเนื่องจากจะตรวจสอบเงื่อนไข end-of-string เพียงครั้งเดียวหลังจากการเปรียบเทียบองค์ประกอบทั้งสี่ในขณะที่การเข้ารหัสมาตรฐานจะเกี่ยวข้องกับการตรวจสอบหนึ่งครั้งต่อการเปรียบเทียบ กล่าวว่าแตกต่างกัน 5 การทดสอบต่อ 4 องค์ประกอบเทียบกับ 8 การทดสอบต่อ 4 องค์ประกอบ

อย่างไรก็ตามจะใช้งานได้ก็ต่อเมื่อความยาวสตริงเป็นผลคูณของสี่หรือมีองค์ประกอบ sentinel (เพื่อรับประกันว่าสตริงทั้งสองจะแตกต่างกันเมื่อผ่านstrendเส้นขอบ) สวยเสี่ยง!


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

@ เดนนิส: คอมไพเลอร์ที่แตกต่างกันมีวิธีการเพิ่มประสิทธิภาพโค้ดที่สร้างขึ้น บางคนอาจทำแบบวนซ้ำคลายตัวเอง (เพื่อขยายบางส่วน) หรือเพิ่มประสิทธิภาพการมอบหมายงาน ที่นี่ coder จะบังคับให้คอมไพเลอร์เข้าสู่ loop-unrolling ทำให้คอมไพเลอร์ที่ปรับให้เหมาะสมน้อยลงทำงานได้ดี ฉันคิดว่า Yves ถูกต้องเกี่ยวกับสมมติฐานของเขา แต่หากไม่มี coder ดั้งเดิมอยู่รอบ ๆ มันก็ยังคงเป็นปริศนาอยู่เล็กน้อยว่าความคิดที่แท้จริงอยู่เบื้องหลังคำพูดที่ "ตลก"
Abel

1
@Abel ขอบคุณสำหรับการชี้แจงฉันเข้าใจความหมาย (สันนิษฐาน) ที่อยู่เบื้องหลังความคิดเห็นได้ดีขึ้นแล้ว Yves เข้ามาใกล้ที่สุดในการไขปริศนาเบื้องหลังความคิดเห็นนี้ แต่ฉันจะยอมรับคำตอบของ Mysticial เพราะฉันคิดว่าเขาตอบคำถามของฉันได้ดีที่สุด ปรากฎว่าฉันถามคำถามผิดเพราะความคิดเห็นทำให้ฉันเข้าใจผิดว่ามุ่งเน้นไปที่ประเภทของการวนซ้ำในขณะที่อาจอ้างถึงเงื่อนไข
Dennis

0

การพูดคุยเรื่องประสิทธิภาพ while กับ do นี้ไม่มีจุดหมายโดยสิ้นเชิงในกรณีนี้เนื่องจากไม่มีเนื้อความ

และ

เทียบเท่ากันอย่างแน่นอน

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