ฉันคิดว่ามันจะมีประโยชน์มากขึ้นสำหรับผู้ถามที่จะได้คำตอบที่แตกต่างกันมากขึ้นเพราะฉันเห็นข้อสมมติฐานหลายข้อที่ไม่ได้ตรวจสอบในคำถามและในบางคำตอบหรือความคิดเห็น
รันไทม์สัมพัทธ์ที่เป็นผลลัพธ์ของการเลื่อนและการคูณไม่มีส่วนเกี่ยวข้องกับ C. เมื่อฉันพูด C ฉันไม่ได้หมายถึงอินสแตนซ์ของการใช้งานเฉพาะเช่น GCC รุ่นนั้นหรือภาษานั้น ฉันไม่ได้ตั้งใจที่จะใช้โฆษณานี้เป็นเรื่องเหลวไหล แต่เพื่อใช้เป็นตัวอย่างในการอธิบาย: คุณสามารถใช้คอมไพเลอร์ C ที่ได้มาตรฐานที่สมบูรณ์และมีการคูณใช้เวลาหนึ่งชั่วโมง ฉันไม่ได้ตระหนักถึงข้อ จำกัด ด้านประสิทธิภาพดังกล่าวใน C หรือ C ++
คุณอาจไม่สนใจเกี่ยวกับเทคนิคนี้ในการโต้แย้ง ความตั้งใจของคุณน่าจะแค่ทดสอบประสิทธิภาพสัมพัทธ์ของการทำกะกับการคูณและคุณเลือก C เพราะโดยทั่วไปแล้วมันถูกมองว่าเป็นภาษาการเขียนโปรแกรมระดับต่ำดังนั้นเราอาจคาดหวังว่าซอร์สโค้ดของมันจะแปลเป็นคำแนะนำที่เกี่ยวข้องโดยตรง คำถามดังกล่าวเป็นเรื่องธรรมดามากและฉันคิดว่าคำตอบที่ดีควรชี้ให้เห็นว่าแม้ใน C ซอร์สโค้ดของคุณจะไม่แปลเป็นคำแนะนำโดยตรงเพราะคุณอาจคิดในตัวอย่างที่กำหนด ฉันให้ผลการรวบรวมที่เป็นไปได้ด้านล่างนี้กับคุณ
นี่คือที่ความคิดเห็นที่ถามถึงประโยชน์ของการแทนที่ความเท่าเทียมกันในซอฟต์แวร์แห่งความเป็นจริงในโลกนี้คุณสามารถเห็นความคิดเห็นบางส่วนในคำถามของคุณเช่นที่มาจาก Eric Lippert มันสอดคล้องกับปฏิกิริยาที่คุณมักจะได้รับจากวิศวกรที่มีประสบการณ์มากขึ้นเพื่อตอบสนองต่อการเพิ่มประสิทธิภาพดังกล่าว หากคุณใช้การเปลี่ยนแปลงแบบไบนารีในรหัสการผลิตเป็นวิธีการครอบคลุมในการคูณและหารคนส่วนใหญ่จะประจบประแจงรหัสของคุณและมีปฏิกิริยาทางอารมณ์ในระดับหนึ่ง ("ฉันเคยได้ยินคำกล่าวอ้างที่ไร้สาระเกี่ยวกับ JavaScript เพื่อประโยชน์ของสวรรค์") มันอาจไม่สมเหตุสมผลนักโปรแกรมเมอร์มือใหม่เว้นแต่พวกเขาจะเข้าใจเหตุผลของปฏิกิริยาเหล่านั้นได้ดีขึ้น
เหตุผลเหล่านั้นส่วนใหญ่เป็นการรวมกันของความสามารถในการอ่านที่ลดลงและความไม่ได้ผลของการเพิ่มประสิทธิภาพดังกล่าวเนื่องจากคุณอาจพบว่ามีการเปรียบเทียบประสิทธิภาพที่เกี่ยวข้อง อย่างไรก็ตามฉันไม่คิดว่าผู้คนจะมีปฏิกิริยาที่แข็งแกร่งหากการแทนที่การเปลี่ยนแปลงการคูณเป็นเพียงตัวอย่างเดียวของการปรับให้เหมาะสมดังกล่าว คำถามเช่นคุณมักเกิดขึ้นในหลายรูปแบบและในบริบทต่างๆ ฉันคิดว่าสิ่งที่วิศวกรอาวุโสตอบสนองต่อการตอบโต้อย่างรุนแรงอย่างน้อยก็บางครั้งฉันก็มีความเป็นไปได้ที่จะเกิดอันตรายในวงกว้างมากขึ้นเมื่อผู้คนใช้การเพิ่มประสิทธิภาพขนาดเล็กเช่นนี้อย่างเสรีในฐานรหัส หากคุณทำงานกับ บริษัท อย่าง Microsoft บนฐานรหัสขนาดใหญ่คุณจะใช้เวลาในการอ่านซอร์สโค้ดของวิศวกรคนอื่นเป็นจำนวนมากหรือพยายามค้นหารหัสที่แน่นอนในนั้น อาจเป็นรหัสของคุณเองที่คุณจะพยายามทำความเข้าใจในเวลาไม่กี่ปีโดยเฉพาะอย่างยิ่งในช่วงเวลาที่ไม่เหมาะสมที่สุดเช่นในกรณีที่คุณต้องแก้ไขปัญหาการหยุดทำงานเนื่องจากการโทรที่คุณได้รับจากเพจเจอร์ หน้าที่ในคืนวันศุกร์กำลังจะออกไปเที่ยวสนุกกับเพื่อน ๆ ... ถ้าคุณใช้เวลามากกับการอ่านรหัสคุณจะประทับใจกับการอ่านมากที่สุด ลองนึกภาพการอ่านนวนิยายที่คุณชื่นชอบ แต่สำนักพิมพ์ได้ตัดสินใจที่จะเปิดตัวรุ่นใหม่ที่พวกเขาใช้ abbrv ทั้งหมด ovr th plc bcs เจ้า thnk มัน svs spc นั่นคล้ายกับปฏิกิริยาที่วิศวกรคนอื่นอาจมีต่อรหัสของคุณหากคุณโรยมันด้วยการปรับให้เหมาะสมดังกล่าว ตามที่คำตอบอื่น ๆ ชี้ให้เห็นจะเป็นการดีกว่าที่จะระบุอย่างชัดเจนว่าคุณหมายถึงอะไร
แม้ว่าในสภาพแวดล้อมเหล่านั้นคุณอาจพบว่าตัวเองกำลังตอบคำถามสัมภาษณ์ที่คุณคาดว่าจะรู้เรื่องนี้หรือสิ่งอื่นที่เทียบเท่า รู้ว่าพวกเขาไม่ได้เลวร้ายและวิศวกรที่ดีจะต้องตระหนักถึงผลกระทบทางคณิตศาสตร์ของการขยับแบบไบนารี โปรดทราบว่าฉันไม่ได้พูดว่าสิ่งนี้ทำให้วิศวกรที่ดี แต่วิศวกรที่ดีจะรู้ว่าในความคิดของฉัน โดยเฉพาะอย่างยิ่งคุณยังอาจพบผู้จัดการบางคนโดยปกติแล้วในตอนท้ายของบทสัมภาษณ์ของคุณซึ่งจะยิ้มให้คุณอย่างคาดหวังว่าจะมีความสุขที่จะเปิดเผย "เคล็ดลับ" ทางวิศวกรรมอันชาญฉลาดนี้ให้คุณในคำถามที่เข้ารหัสและพิสูจน์ว่าเขา / เธอ เช่นเคยเป็นหรือเป็นหนึ่งในวิศวกรที่ชาญฉลาดและไม่ใช่ "เพียงแค่" ผู้จัดการ ในสถานการณ์เหล่านั้นเพียงแค่พยายามมองความประทับใจและขอบคุณเขา / เธอสำหรับการสัมภาษณ์ที่รู้แจ้ง
ทำไมคุณไม่เห็นความแตกต่างความเร็วใน C? คำตอบที่เป็นไปได้มากที่สุดคือทั้งคู่ส่งผลให้เกิดรหัสแอสเซมบลีเดียวกัน:
int shift(int i) { return i << 2; }
int multiply(int i) { return i * 2; }
สามารถรวบรวมได้ทั้งคู่
shift(int):
lea eax, [0+rdi*4]
ret
ใน GCC โดยไม่มีการปรับให้เหมาะสมเช่นใช้แฟล็ก "-O0" คุณอาจได้รับสิ่งนี้:
shift(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
sal eax, 2
pop rbp
ret
multiply(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, eax
pop rbp
ret
อย่างที่คุณเห็นการส่งผ่าน "-O0" ไปยัง GCC ไม่ได้หมายความว่ามันจะไม่ฉลาดพอที่จะเข้าใจเกี่ยวกับโค้ดที่สร้างขึ้น โดยเฉพาะอย่างยิ่งสังเกตว่าแม้ในกรณีนี้คอมไพเลอร์หลีกเลี่ยงการใช้คำสั่งคูณ คุณสามารถทำซ้ำการทดสอบเดียวกันโดยใช้การเลื่อนด้วยตัวเลขอื่นและแม้แต่การคูณด้วยตัวเลขที่ไม่ใช่พลังของสอง มีโอกาสเกิดขึ้นบนแพลตฟอร์มของคุณคุณจะเห็นการรวมกันของการเปลี่ยนแปลงและการเพิ่มเติม แต่ไม่มีการคูณ ดูเหมือนว่าเป็นเรื่องบังเอิญสำหรับคอมไพเลอร์ที่จะหลีกเลี่ยงการใช้การคูณในทุกกรณีหากการคูณและการเลื่อนมีค่าใช้จ่ายเท่ากันจริง ๆ ไม่ใช่หรือ? แต่ฉันไม่ได้ตั้งใจที่จะจัดหาหลักฐานเพื่อพิสูจน์ดังนั้นให้เราไปต่อ
คุณสามารถรันการทดสอบอีกครั้งด้วยรหัสข้างต้นและดูว่าคุณสังเกตเห็นความแตกต่างความเร็วตอนนี้หรือไม่ แล้วถึงแม้คุณจะไม่ได้รับการทดสอบการเปลี่ยนแปลงเมื่อเทียบกับการคูณที่คุณสามารถดูโดยไม่มีการคูณที่ว่า แต่รหัสที่ถูกสร้างขึ้นด้วยบางชุดของธงโดย GCC สำหรับการดำเนิน C ของการเปลี่ยนแปลงและคูณในอินสแตนซ์โดยเฉพาะอย่างยิ่ง ดังนั้นในการทดสอบอื่นคุณสามารถแก้ไขรหัสแอสเซมบลีด้วยตนเองและแทนที่จะใช้คำสั่ง "imul" ในรหัสสำหรับวิธีการ "คูณ"
หากคุณต้องการกำจัดสมาร์ทโฟนของคอมไพเลอร์บางตัวคุณสามารถกำหนดวิธีการทั่วไปและการทวีคูณมากขึ้นและจะจบลงด้วยสิ่งต่อไปนี้:
int shift(int i, int j) { return i << j; }
int multiply(int i, int j) { return i * j; }
ซึ่งอาจให้รหัสการประกอบต่อไปนี้:
shift(int, int):
mov eax, edi
mov ecx, esi
sal eax, cl
ret
multiply(int, int):
mov eax, edi
imul eax, esi
ret
ในที่สุดเราก็มีที่นี่แม้ในระดับการเพิ่มประสิทธิภาพสูงสุดของ GCC 4.9 การแสดงออกในคำแนะนำการประกอบที่คุณอาจคาดหวังเมื่อคุณเริ่มต้นการทดสอบของคุณ ฉันคิดว่าในตัวมันเองอาจเป็นบทเรียนสำคัญในการเพิ่มประสิทธิภาพ เราสามารถเห็นความแตกต่างที่เกิดขึ้นเพื่อแทนที่ตัวแปรสำหรับค่าคงที่ที่เป็นรูปธรรมในรหัสของเราในแง่ของสมาร์ทที่คอมไพเลอร์สามารถใช้งานได้ การปรับให้เหมาะสมที่สุดแบบไมโครเช่นการทดแทนการเลื่อนแบบทวีคูณเป็นการเพิ่มประสิทธิภาพระดับต่ำมากที่คอมไพเลอร์สามารถทำได้ง่าย ๆ ด้วยตัวเอง การเพิ่มประสิทธิภาพอื่น ๆ ที่ส่งผลกระทบต่อประสิทธิภาพมากขึ้นนั้นจำเป็นต้องมีความเข้าใจในเจตนาของรหัสที่มักจะไม่สามารถเข้าถึงได้โดยคอมไพเลอร์หรือสามารถคาดเดาได้โดยการแก้ปัญหาบางอย่าง นั่นคือสิ่งที่คุณเป็นวิศวกรซอฟต์แวร์เข้ามาและแน่นอนว่าโดยทั่วไปแล้วจะไม่เกี่ยวข้องกับการแทนที่การคูณด้วยการเลื่อน มันเกี่ยวข้องกับปัจจัยต่าง ๆ เช่นการหลีกเลี่ยงการโทรซ้ำซ้อนไปยังบริการที่สร้าง I / O และสามารถบล็อกกระบวนการ หากคุณไปที่ฮาร์ดดิสก์ของคุณหรือห้ามมิให้ไปยังฐานข้อมูลระยะไกลสำหรับข้อมูลเพิ่มเติมบางอย่างที่คุณอาจได้รับจากสิ่งที่คุณมีอยู่ในหน่วยความจำ ตอนนี้ฉันคิดว่าเราหลงทางไปไกลจากคำถามเดิมของคุณแล้ว แต่ฉันคิดว่าการชี้ไปที่ผู้ถามโดยเฉพาะอย่างยิ่งถ้าเราคิดว่าคนที่เพิ่งเริ่มเข้าใจการแปลและการใช้รหัส
แล้วอันไหนจะเร็วกว่ากัน? ฉันคิดว่ามันเป็นวิธีการที่ดีที่คุณเลือกที่จะทดสอบความแตกต่างของประสิทธิภาพ โดยทั่วไปแล้วมันเป็นเรื่องง่ายที่จะประหลาดใจโดยประสิทธิภาพการทำงานของการเปลี่ยนแปลงรหัสบางอย่าง มีเทคนิคมากมายที่ใช้โปรเซสเซอร์ที่ทันสมัยและการทำงานร่วมกันระหว่างซอฟต์แวร์อาจซับซ้อนเช่นกัน แม้ว่าคุณควรจะได้รับผลการดำเนินงานที่เป็นประโยชน์สำหรับการเปลี่ยนแปลงบางอย่างในสถานการณ์เดียวฉันคิดว่ามันเป็นเรื่องอันตรายที่จะสรุปว่าการเปลี่ยนแปลงประเภทนี้จะให้ประโยชน์ด้านประสิทธิภาพเสมอ ฉันคิดว่ามันอันตรายที่จะทำการทดสอบครั้งเดียวพูดว่า "โอเคตอนนี้ฉันรู้แล้วว่าตัวไหนเร็วกว่ากัน!" และใช้การปรับให้เหมาะสมแบบเดียวกันกับรหัสการผลิตโดยไม่ทำการวัดซ้ำของคุณ
แล้วถ้ากะเร็วกว่าการคูณล่ะ มีข้อบ่งชี้อย่างแน่นอนว่าทำไมถึงเป็นจริง GCC ดังที่คุณเห็นด้านบนดูเหมือนจะคิด (แม้ไม่มีการเพิ่มประสิทธิภาพ) ที่หลีกเลี่ยงการคูณโดยตรงในคำแนะนำอื่น ๆ เป็นความคิดที่ดี Intel 64 และ IA-32 สถาปัตยกรรมการเพิ่มประสิทธิภาพ Reference Manualจะให้ความคิดของค่าใช้จ่ายญาติของคำแนะนำ CPU แหล่งข้อมูลอื่นที่มุ่งเน้นที่ความล่าช้าในการเรียนการสอนและปริมาณงานคือhttp://www.agner.org/optimize/instruction_tables.pdf. โปรดทราบว่ามันไม่ใช่ตัวบ่งชี้ที่ดีของรันไทม์สัมบูรณ์ แต่ประสิทธิภาพของคำสั่งที่สัมพันธ์กัน ในการวนรอบที่แน่นขณะที่การทดสอบของคุณกำลังจำลองตัวชี้วัดของ "ปริมาณงาน" ควรเกี่ยวข้องมากที่สุด เป็นจำนวนรอบที่หน่วยการดำเนินการโดยทั่วไปจะถูกผูกไว้เมื่อดำเนินการคำสั่งที่กำหนด
แล้วถ้ากะไม่เร็วกว่าการคูณล่ะ ดังที่ฉันได้กล่าวไว้ข้างต้นสถาปัตยกรรมสมัยใหม่อาจมีความซับซ้อนและสิ่งต่าง ๆ เช่นการคาดคะเนสาขาการแคชการไพพ์ไลน์และการประมวลผลแบบขนานทำให้ยากต่อการคาดการณ์ประสิทธิภาพสัมพัทธ์ของโค้ดสองชิ้นที่มีเหตุผลในเวลาเดียวกัน ฉันต้องการเน้นย้ำเพราะนี่คือที่ที่ฉันไม่มีความสุขกับคำตอบของคำถามส่วนใหญ่เช่นนี้และกับค่ายผู้คนทันทีบอกว่ามันไม่จริง (อีกต่อไป) ที่ขยับเร็วกว่าการคูณ
ไม่เท่าที่ฉันรู้ว่าเราไม่ได้คิดค้นซอสวิศวกรรมลับในปี 1970 หรือเมื่อใดก็ตามที่จะยกเลิกความแตกต่างค่าใช้จ่ายของหน่วยคูณและจำแลงเล็กน้อยในทันที การคูณทั่วไปในแง่ของประตูตรรกะและแน่นอนในแง่ของการดำเนินการทางตรรกะยังคงมีความซับซ้อนมากกว่าการเปลี่ยนแปลงด้วยตัวเปลี่ยนลำกล้องในหลาย ๆ สถานการณ์บนสถาปัตยกรรมจำนวนมาก วิธีนี้แปลเป็นรันไทม์โดยรวมบนคอมพิวเตอร์เดสก์ท็อปอาจเป็นทึบเล็กน้อย ฉันไม่รู้ว่าจะใช้งานอย่างไรในโปรเซสเซอร์เฉพาะ แต่นี่เป็นคำอธิบายของการคูณ: การคูณจำนวนเต็มเป็นความเร็วเดียวกับการเพิ่ม CPU สมัยใหม่หรือไม่
ในขณะที่ที่นี่เป็นคำอธิบายของBarrel Shifter เอกสารที่ฉันอ้างถึงในวรรคก่อนหน้านี้ให้มุมมองอื่นเกี่ยวกับค่าใช้จ่ายในการดำเนินงานโดยอ้างอิงจากคำสั่งของ CPU วิศวกรที่ทำงานกับ Intel มักจะได้รับคำถามที่คล้ายกัน: วงจรนาฬิกาของนักพัฒนา Intel รอบนาฬิกาสำหรับการคูณจำนวนเต็มและเพิ่มเติมในตัวประมวลผล core 2 duo
ใช่ในสถานการณ์ส่วนใหญ่ในชีวิตจริงและเกือบจะแน่นอนใน JavaScript การพยายามใช้ประโยชน์จากความเท่าเทียมนี้เพื่อประโยชน์ของการทำงานน่าจะเป็นสิ่งที่ไร้ประโยชน์ อย่างไรก็ตามแม้ว่าเราบังคับให้ใช้คำแนะนำการคูณและจากนั้นก็ไม่เห็นความแตกต่างในเวลาทำงานนั่นเป็นเพราะลักษณะของตัวชี้วัดต้นทุนที่เราใช้มีความแม่นยำและไม่ได้เพราะไม่มีความแตกต่างค่าใช้จ่าย รันไทม์จากต้นจนจบเป็นหนึ่งเมตริกและถ้าเป็นสิ่งเดียวที่เราใส่ใจทุกอย่างก็ดี แต่นั่นไม่ได้หมายความว่าความแตกต่างของค่าใช้จ่ายทั้งหมดระหว่างการคูณและการเปลี่ยนจะหายไปอย่างง่ายดาย และฉันคิดว่ามันไม่ใช่ความคิดที่ดีที่จะถ่ายทอดความคิดนั้นไปยังผู้ถามโดยนัยหรืออย่างอื่นซึ่งเห็นได้ชัดว่าเพิ่งเริ่มได้รับแนวคิดเกี่ยวกับปัจจัยที่เกี่ยวข้องกับเวลาทำงานและค่าใช้จ่ายของรหัสสมัยใหม่ วิศวกรรมเป็นเรื่องเกี่ยวกับการแลกเปลี่ยน การสอบถามและคำอธิบายเกี่ยวกับสิ่งที่หน่วยประมวลผลสมัยใหม่ได้ทำการแลกเปลี่ยนเพื่อแสดงเวลาดำเนินการที่เราในฐานะผู้ใช้เห็นอาจให้คำตอบที่แตกต่างกันมากขึ้น และฉันคิดว่าคำตอบที่แตกต่างกว่า "นี่ไม่ใช่ความจริงอีกต่อไป" เป็นสิ่งรับประกันถ้าเราต้องการเห็นวิศวกรน้อยลงในการใช้รหัสขนาดจิ๋วที่กำจัดการอ่านได้ง่ายเพราะมันต้องใช้ความเข้าใจโดยทั่วไปเกี่ยวกับลักษณะของ พบว่ามีสาขาที่หลากหลายและหลากหลายกว่าเพียงแค่อ้างถึงอินสแตนซ์ที่เฉพาะเจาะจงบางอย่างที่ล้าสมัย