เมื่อฉันทดสอบความแตกต่างในเวลาระหว่างการเลื่อนและการคูณใน C ไม่มีความแตกต่าง ทำไม?


28

ฉันได้รับการสอนแล้วว่าการขยับเป็นเลขฐานสองนั้นมีประสิทธิภาพมากกว่าการคูณด้วย 2 ^ k ดังนั้นฉันต้องการทดสอบและฉันใช้รหัสต่อไปนี้เพื่อทดสอบ:

#include <time.h>
#include <stdio.h>

int main() {
    clock_t launch = clock();
    int test = 0x01;
    int runs;

    //simple loop that oscillates between int 1 and int 2
    for (runs = 0; runs < 100000000; runs++) {


    // I first compiled + ran it a few times with this:
    test *= 2;

    // then I recompiled + ran it a few times with:
    test <<= 1;

    // set back to 1 each time
    test >>= 1;
    }

    clock_t done = clock();
    double diff = (done - launch);
    printf("%f\n",diff);
}

สำหรับทั้งสองเวอร์ชันการพิมพ์ออกมามีประมาณ 440000 หรือให้หรือรับ 10,000 ไม่มีความแตกต่างอย่างมีนัยสำคัญ (อย่างน้อยมองเห็น) ระหว่างผลลัพธ์ทั้งสองรุ่น ดังนั้นคำถามของฉันคือมีอะไรผิดปกติกับวิธีการของฉัน? ควรจะมีความแตกต่างที่มองเห็นหรือไม่? สิ่งนี้เกี่ยวข้องกับสถาปัตยกรรมของคอมพิวเตอร์คอมไพเลอร์หรืออย่างอื่นหรือไม่


47
คนที่สอนคุณว่าเข้าใจผิดอย่างชัดเจน ความเชื่อนั้นไม่เป็นความจริงตั้งแต่ปี 1970 สำหรับคอมไพเลอร์ที่ใช้งานตามปกติในสถาปัตยกรรมที่ใช้โดยทั่วไป ดีสำหรับคุณในการทดสอบการอ้างสิทธิ์นี้ ฉันเคยได้ยินคำกล่าวอ้างที่ไร้สาระเกี่ยวกับJavaScriptเพื่อประโยชน์ของสวรรค์
Eric Lippert

21
วิธีที่ดีที่สุดในการตอบคำถามเช่นนี้คือดูรหัสประกอบที่คอมไพเลอร์กำลังสร้าง คอมไพเลอร์มักจะมีตัวเลือกในการสร้างสำเนาของภาษาแอสเซมบลีที่พวกเขากำลังสร้าง สำหรับคอมไพเลอร์ GNU GCC นี่คือ '-S'
Charles E. Grant

8
หนึ่งควรชี้ให้เห็นว่าหลังจากดูนี้ด้วยgcc -Sรหัสสำหรับtest *= 2จริง ๆ แล้วรวบรวมshll $1, %eax เมื่อเรียกด้วยgcc -O3 -Sมีแม้ไม่วนซ้ำ การโทรสองนาฬิกานั้นแยกกัน:callq _clock movq %rax, %rbx callq _clock

6
"ฉันได้รับการสอนแล้วว่าการเปลี่ยนเป็นเลขฐานสองนั้นมีประสิทธิภาพมากกว่าการคูณด้วย 2 ^ k"; เราได้รับการสอนหลายสิ่งหลายอย่างที่ผิดไป (หรืออย่างน้อยก็ล้าสมัย) คอมไพเลอร์ที่ฉลาดจะใช้การดำเนินการกะเดียวกันสำหรับทั้งสอง
John Bode

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

คำตอบ:


44

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

นี่เป็นกฎทั่วไปมากเมื่อทำการปรับให้เหมาะสม: 'การเพิ่มประสิทธิภาพ' ส่วนใหญ่จะเข้าใจผิดการรวบรวมเกี่ยวกับสิ่งที่คุณหมายถึงจริง ๆ และอาจลดประสิทธิภาพลง

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

ข้อเสียที่ใหญ่ที่สุดในการเพิ่มประสิทธิภาพก็คือรหัส 'ที่ได้รับการปรับให้เหมาะสม' นั้นมักจะอ่านได้น้อยกว่ามาก ดังนั้นในกรณีของคุณไปคูณเมื่อคุณต้องการคูณ และเปลี่ยนบิตเมื่อคุณต้องการย้ายบิต


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

2
จะมี (พูดจริง) จะต้องเพิ่มประสิทธิภาพการคูณเพื่อดำเนินการ shift ในโปรแกรมซอฟต์แวร์ระดับสูงหรือไม่ ดูเหมือนว่าเนื่องจากคอมไพเลอร์ปรับให้เหมาะสมแล้วเพียงครั้งเดียวที่มีประโยชน์ที่จะมีความรู้นี้คือเมื่อการเขียนโปรแกรมในระดับต่ำมาก (อย่างน้อยด้านล่างคอมไพเลอร์)
NicholasFolk

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

2
@NicholasFolk: การเพิ่มประสิทธิภาพในระดับนี้มักจะถูกบดบังหรือแสดงผลโดยสถาปัตยกรรม CPU อยู่ตลอดเวลา ใครจะสนใจถ้าคุณบันทึก 50 รอบเมื่อเพียงแค่ดึงข้อโต้แย้งจากหน่วยความจำและเขียนมันกลับมาใช้เวลามากกว่า 100? การปรับขนาดเล็กให้เหมาะสมเช่นนี้เหมาะสมเมื่อหน่วยความจำทำงาน (หรือใกล้เคียง) ความเร็วของ CPU แต่ไม่มากในปัจจุบัน
TMN

2
เพราะฉันเบื่อที่จะเห็นว่า 10% ของราคาที่อ้างและเพราะมันกระทบเล็บที่หัวที่นี่: "ไม่ต้องสงสัยเลยว่าจอกของประสิทธิภาพนำไปสู่การละเมิดโปรแกรมเมอร์ไม่ต้องเสียเวลาจำนวนมากคิดหรือกังวล เกี่ยวกับความเร็วของส่วนที่ไม่สำคัญของโปรแกรมและความพยายามเหล่านี้อย่างมีประสิทธิภาพมีผลกระทบเชิงลบอย่างมากเมื่อพิจารณาการดีบั๊กและการบำรุงรักษาเราควรลืมประสิทธิภาพเล็ก ๆ น้อย ๆ พูดถึง 97% ของเวลา: การเพิ่มประสิทธิภาพก่อนวัยอันควร ความชั่วทั้งหมด ...
cHao

25

คอมไพเลอร์รับรู้ค่าคงที่และแปลงทวีคูณเป็นค่ากะตามความเหมาะสม


คอมไพเลอร์รับรู้ค่าคงที่ที่เป็นพลังของ 2 .... และแปลงเป็นกะ ค่าคงที่ทั้งหมดไม่สามารถเปลี่ยนเป็นกะได้
quick_now

4
@quickly_now: สามารถแปลงเป็นการรวมกันของกะและการบวก / การลบ
Mehrdad

2
ข้อบกพร่องของคอมไพเลอร์ออพติไมเซอร์คลาสสิกคือการแปลงแบ่งออกเป็นกะขวาซึ่งทำงานเพื่อเงินปันผลที่เป็นบวก แต่ถูกปิดด้วย 1 สำหรับค่าลบ
ddyer

1
@quickly_now ฉันเชื่อว่าคำว่า 'เหมาะสม' จะครอบคลุมความคิดที่ว่าค่าคงที่บางค่าไม่สามารถเขียนใหม่เป็นกะได้
Pharap

21

การขยับนั้นเร็วกว่าการคูณหรือไม่นั้นขึ้นอยู่กับสถาปัตยกรรมของ CPU ของคุณ ย้อนกลับไปในสมัยของ Pentium และก่อนหน้านี้การขยับมักเร็วกว่าการคูณขึ้นอยู่กับจำนวน 1 บิตในการคูณของคุณ ตัวอย่างเช่นหาก Multiplicand ของคุณเป็น 320 นั่นคือ 101000000 สองบิต

a *= 320;               // Slower
a = (a<<7) + (a<<9);    // Faster

แต่ถ้าคุณมีมากกว่าสองบิต ...

a *= 324;                        // About same speed
a = (a<<2) + (a<<7) + (a<<9);    // About same speed

a *= 340;                                 // Faster
a = (a<<2) + (a<<4) + (a<<7) + (a<<9);    // Slower

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

a  *= 2;   // Exactly the same speed
a <<= 1;   // Exactly the same speed

a  *= 4;   // Faster
a <<= 2;   // Slower

โปรดทราบว่านั่นคือสิ่งที่ตรงกันข้ามกับสิ่งที่เป็นจริงใน CPU ของ Intel รุ่นเก่า

แต่มันก็ไม่ง่ายอย่างนั้น ถ้าฉันจำได้อย่างถูกต้องเนื่องจากสถาปัตยกรรม Superscalar ของมัน Pentium สามารถประมวลผลคำสั่งการคูณหนึ่งคำสั่งหรือคำแนะนำการเปลี่ยนสองคำพร้อมกัน (ตราบเท่าที่พวกเขาไม่ได้ขึ้นอยู่กับแต่ละอื่น ๆ ) ซึ่งหมายความว่าหากคุณต้องการคูณตัวแปรสองตัวด้วยกำลัง 2 การขยับอาจจะดีกว่า

a  *= 4;   // 
b  *= 4;   // 

a <<= 2;   // Both lines execute in a single cycle
b <<= 2;   // 

5
+1 "การขยับเร็วกว่าการคูณขึ้นอยู่กับสถาปัตยกรรมของ CPU ของคุณหรือไม่" ขอบคุณสำหรับการเข้าสู่ประวัติศาสตร์เล็กน้อยและแสดงให้เห็นว่าตำนานคอมพิวเตอร์ส่วนใหญ่มีพื้นฐานทางตรรกะอยู่บ้าง
Pharap

11

คุณประสบปัญหาหลายประการกับโปรแกรมทดสอบของคุณ

testครั้งแรกที่คุณไม่ได้ใช้ค่าของ ไม่มีวิธีใดในมาตรฐาน C ว่าคุณค่าของtestเรื่อง เครื่องมือเพิ่มประสิทธิภาพนี้สามารถลบออกได้อย่างสมบูรณ์ฟรี เมื่อลบออกแล้วลูปของคุณจะว่างเปล่า เอฟเฟกต์ที่มองเห็นได้เพียงอย่างเดียวคือตั้งค่าruns = 100000000แต่runsไม่ได้ใช้งาน ดังนั้นเครื่องมือเพิ่มประสิทธิภาพสามารถ (และควร!) ลบลูปทั้งหมด แก้ไขได้ง่าย: พิมพ์ค่าที่คำนวณด้วย โปรดทราบว่าเครื่องมือเพิ่มประสิทธิภาพที่กำหนดไว้อย่างเพียงพอยังคงสามารถปรับลูปให้เหมาะสมได้ (ขึ้นอยู่กับค่าคงที่ที่ทราบในเวลารวบรวม)

ประการที่สองคุณดำเนินการสองอย่างที่ยกเลิกซึ่งกันและกัน เครื่องมือเพิ่มประสิทธิภาพได้รับอนุญาตให้สังเกตสิ่งนี้และยกเลิกได้ ออกจากวนซ้ำที่ว่างเปล่าอีกครั้งและลบออก อันนี้ยากที่จะแก้ไขอย่างจริงจัง คุณสามารถสลับไปที่unsigned int(ดังนั้นการโอเวอร์โฟลว์ไม่ใช่พฤติกรรมที่ไม่ได้กำหนด) แต่แน่นอนว่าผลลัพธ์ใน 0 และสิ่งที่เรียบง่าย (เช่นพูดว่าtest += 1) นั้นง่ายพอสำหรับเครื่องมือเพิ่มประสิทธิภาพในการคิดและทำ

ในที่สุดคุณคิดว่าtest *= 2จริง ๆ แล้วจะถูกรวบรวมเป็นทวีคูณ นั่นเป็นการเพิ่มประสิทธิภาพที่ง่ายมาก ถ้า bitshift เร็วขึ้นเครื่องมือเพิ่มประสิทธิภาพจะใช้แทน ในการหลีกเลี่ยงปัญหานี้คุณจะต้องใช้บางสิ่งบางอย่างเช่นชุดประกอบเฉพาะสำหรับการใช้งาน

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

เมื่อฉันตรวจสอบแอสเซมบลีเอาท์พุทของการคอมไพล์โปรแกรมของคุณด้วยการgcc -S -O3ใช้เวอร์ชัน 4.9 เครื่องมือเพิ่มประสิทธิภาพจะเห็นทุกรูปแบบที่เรียบง่ายด้านบนและอีกมากมาย ในทุกกรณีมันออกวง (การกำหนดค่าคงที่ไปtest) สิ่งเดียวที่เหลือก็สายไปclock()ที่แปลง / printfลบและ


1
โปรดทราบว่าเครื่องมือเพิ่มประสิทธิภาพสามารถ (และจะ) เพิ่มประสิทธิภาพการดำเนินการกับค่าคงที่ (แม้จะอยู่ในลูป) ดังที่แสดงในsqrt c # vs sqrt c ++ซึ่งเครื่องมือเพิ่มประสิทธิภาพสามารถแทนที่ลูปที่รวมค่าด้วยผลรวมจริง ในการกำจัดการปรับให้เหมาะสมนั้นคุณต้องใช้บางสิ่งที่กำหนดไว้ที่รันไทม์ (เช่นอาร์กิวเมนต์บรรทัดคำสั่ง)

@MichaelT ใช่ นั่นคือสิ่งที่ฉันหมายถึงโดย "โปรดสังเกตว่าเครื่องมือเพิ่มประสิทธิภาพที่กำหนดไว้อย่างเพียงพอยังสามารถปรับลูปให้เหมาะสมได้ (ขึ้นอยู่กับค่าคงที่ที่รู้กัน ณ เวลารวบรวม)
Derobert

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

@AkshayLAradhya ฉันไม่สามารถพูดได้ว่าคอมไพเลอร์ของคุณทำอะไร แต่ฉันยืนยันอีกครั้งว่าgcc -O3(ตอนนี้ด้วย 7.3) ยังคงลบวงทั้งหมด (ตรวจสอบให้แน่ใจว่าได้สลับเป็น long แทนที่จะเป็น int หากจำเป็นมิฉะนั้นจะปรับให้เหมาะสมเป็นการวนซ้ำไม่สิ้นสุดเนื่องจากการโอเวอร์โฟลว์)
Derobert

8

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

รันไทม์สัมพัทธ์ที่เป็นผลลัพธ์ของการเลื่อนและการคูณไม่มีส่วนเกี่ยวข้องกับ 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 การพยายามใช้ประโยชน์จากความเท่าเทียมนี้เพื่อประโยชน์ของการทำงานน่าจะเป็นสิ่งที่ไร้ประโยชน์ อย่างไรก็ตามแม้ว่าเราบังคับให้ใช้คำแนะนำการคูณและจากนั้นก็ไม่เห็นความแตกต่างในเวลาทำงานนั่นเป็นเพราะลักษณะของตัวชี้วัดต้นทุนที่เราใช้มีความแม่นยำและไม่ได้เพราะไม่มีความแตกต่างค่าใช้จ่าย รันไทม์จากต้นจนจบเป็นหนึ่งเมตริกและถ้าเป็นสิ่งเดียวที่เราใส่ใจทุกอย่างก็ดี แต่นั่นไม่ได้หมายความว่าความแตกต่างของค่าใช้จ่ายทั้งหมดระหว่างการคูณและการเปลี่ยนจะหายไปอย่างง่ายดาย และฉันคิดว่ามันไม่ใช่ความคิดที่ดีที่จะถ่ายทอดความคิดนั้นไปยังผู้ถามโดยนัยหรืออย่างอื่นซึ่งเห็นได้ชัดว่าเพิ่งเริ่มได้รับแนวคิดเกี่ยวกับปัจจัยที่เกี่ยวข้องกับเวลาทำงานและค่าใช้จ่ายของรหัสสมัยใหม่ วิศวกรรมเป็นเรื่องเกี่ยวกับการแลกเปลี่ยน การสอบถามและคำอธิบายเกี่ยวกับสิ่งที่หน่วยประมวลผลสมัยใหม่ได้ทำการแลกเปลี่ยนเพื่อแสดงเวลาดำเนินการที่เราในฐานะผู้ใช้เห็นอาจให้คำตอบที่แตกต่างกันมากขึ้น และฉันคิดว่าคำตอบที่แตกต่างกว่า "นี่ไม่ใช่ความจริงอีกต่อไป" เป็นสิ่งรับประกันถ้าเราต้องการเห็นวิศวกรน้อยลงในการใช้รหัสขนาดจิ๋วที่กำจัดการอ่านได้ง่ายเพราะมันต้องใช้ความเข้าใจโดยทั่วไปเกี่ยวกับลักษณะของ พบว่ามีสาขาที่หลากหลายและหลากหลายกว่าเพียงแค่อ้างถึงอินสแตนซ์ที่เฉพาะเจาะจงบางอย่างที่ล้าสมัย


6

สิ่งที่คุณเห็นคือผลของเครื่องมือเพิ่มประสิทธิภาพ

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

ใน PRINCIPLE การเรียกไปยังไลบรารีการคูณหรือบ่อยครั้งแม้แต่การใช้ตัวคูณฮาร์ดแวร์จะช้ากว่าการเปลี่ยนบิต

ดังนั้น ... หากคอมไพเลอร์ไร้เดียงสาสร้างการเรียกไปยังไลบรารีสำหรับการดำเนินการ * 2 แน่นอนว่ามันจะทำงานช้ากว่าการเลื่อนระดับบิต *

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

เป็นเรื่องที่น่าสนใจวันนี้ฉันแค่มองไปที่แอสเซมเบลอร์ที่สร้างขึ้นสำหรับการดำเนินการบางอย่างเช่น * 5 ... ไม่ได้มองอย่างนั้น แต่อย่างอื่นและตามที่ฉันสังเกตเห็นว่าคอมไพเลอร์เปลี่ยน * 5 เป็น:

  • เปลี่ยน
  • เปลี่ยน
  • เพิ่มหมายเลขเดิม

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

ศิลปะของการเพิ่มประสิทธิภาพคอมไพเลอร์เป็นเรื่องแยกกันเต็มไปด้วยเวทมนตร์และเข้าใจอย่างถูกต้องโดยประมาณ 6 คนบนโลกทั้งหมด :)


3

ลองกำหนดเวลาด้วย:

for (runs = 0; runs < 100000000; runs++) {
      ;
}

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


2

การคูณคือการรวมกันของการเปลี่ยนแปลงและการเพิ่มเติม

ในกรณีที่คุณกล่าวถึงฉันไม่เชื่อว่าสำคัญว่าคอมไพเลอร์ปรับให้เหมาะสมหรือไม่ - "คูณxด้วยสอง" สามารถนำไปใช้เป็น:

  • xเลื่อนบิตของที่เดียวไปทางซ้าย
  • เพิ่มไปxx

เหล่านี้คือการดำเนินการปรมาณูพื้นฐาน หนึ่งไม่เร็วกว่าที่อื่น

เปลี่ยนเป็น "คูณxด้วยสี่", (หรืออะไรก็ได้2^k, k>1) และมันต่างกันเล็กน้อย:

  • xเลื่อนบิตของสองตำแหน่งไปทางซ้าย
  • เพิ่มxไปxและเรียกว่าyเพิ่มไปyy

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

ลองใช้อันหลัง (หรือใด ๆ2^k, k>1) พร้อมตัวเลือกที่เหมาะสมเพื่อป้องกันไม่ให้คุณปรับพวกเขาให้เป็นแบบเดียวกันในการใช้งาน คุณควรจะหาการเปลี่ยนแปลงได้เร็วขึ้น, การเมื่อเทียบกับนอกจากนี้ซ้ำในO(1)O(k)

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


1
"การปฏิบัติการปรมาณูขั้นพื้นฐาน" คืออะไร? ไม่มีใครโต้แย้งได้ว่าในการเปลี่ยนการดำเนินการสามารถนำไปใช้กับทุก ๆ บิตในแบบขนานในขณะที่การเพิ่มบิตซ้ายสุดขึ้นอยู่กับบิตอื่น ๆ ?
Bergi

2
@Bergi: ฉันเดาว่าเขาหมายความว่าทั้งคำสั่ง shift และ add เป็นคำสั่งในเครื่องเดียว คุณต้องดูเอกสารชุดคำสั่งเพื่อดูจำนวนรอบของแต่ละรายการ แต่ใช่การเพิ่มมักจะเป็นการดำเนินการหลายรอบในขณะที่การเปลี่ยนแปลงจะดำเนินการในรอบเดียว
TMN

ใช่ว่าอาจจะมีกรณี แต่คูณเป็นคำแนะนำเครื่องเดียวเช่นกัน ( แต่แน่นอนมันอาจจะต้องรอบเพิ่มเติม)
Bergi

@Bergi นั่นก็ขึ้นอยู่กับส่วนโค้งด้วยเช่นกัน คุณคิดว่าส่วนโค้งใดที่มีการเปลี่ยนแปลงในรอบน้อยกว่าการเพิ่มแบบ 32 บิต (หรือ x-bit ตามความเหมาะสม)
OJFord

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

1

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

อย่างไรก็ตามควรสังเกตว่าการแบ่งค่าเซ็นชื่อที่อาจเป็นลบนั้นไม่เทียบเท่ากับการเลื่อนขวา การแสดงออกเหมือนจะไม่เทียบเท่ากับ(x+8)>>4 (x+8)/16อดีตในคอมไพเลอร์ 99% จะจับคู่ค่าจาก -24 ถึง -9 ถึง -1, -8 ถึง +7 ถึง 0 และ +8 ถึง +23 ถึง 1 [หมายเลขปัดเศษเกือบสมมาตรเกี่ยวกับศูนย์] หลังจะแมป -39 ถึง -24 ถึง -1, -23 ถึง +7 ถึง 0 และ +8 ถึง +23 ถึง +1 [ไม่สมมาตรอย่างไม่มีการลดและไม่น่าจะเป็นไปตามที่ตั้งใจไว้] โปรดทราบว่าแม้เมื่อค่าไม่คาดว่าจะเป็นค่าลบการใช้>>4จะมีแนวโน้มที่จะให้โค้ดเร็วกว่า/16เว้นแต่ว่าคอมไพเลอร์สามารถพิสูจน์ได้ว่าค่านั้นไม่สามารถลบได้


0

ข้อมูลเพิ่มเติมฉันเพิ่งเช็คเอาท์

บน x86_64 MUL opcode มีเวลา 10 รอบแฝงและ 1/2 รอบการรับส่งข้อมูล MOV, ADD และ SHL มีความหน่วงแฝงเท่ากับ 1 รอบด้วยความเร็ว 2.5, 2.5 และ 1.7 รอบการผลิต

การคูณด้วย 15 จะต้องใช้ 3 SHL และ 3 ADD อย่างน้อยที่สุดและอาจเป็น MOV สองสามตัว

https://gmplib.org/~tege/x86-timing.pdf


0

วิธีการของคุณมีข้อบกพร่อง การเพิ่มการวนซ้ำและการตรวจสอบสภาพของคุณนั้นใช้เวลานานมาก

  • ลองใช้ลูปเปล่าแล้ววัดเวลา (เรียกว่าbase)
  • ตอนนี้เพิ่มการดำเนินการกะ 1 ครั้งและวัดเวลา (เรียกว่าs1)
  • ถัดไปเพิ่ม 10 shift shift และวัดเวลา (เรียกว่าs2)

ถ้าทุกอย่างเป็นไปอย่างถูกต้องbase-s2ควรจะเป็น 10 base-s1ครั้งกว่า มิฉะนั้นจะมีสิ่งอื่นเข้ามาเล่นที่นี่

ตอนนี้ฉันลองทำเองแล้วลองคิดดูว่าถ้าลูปเป็นต้นเหตุของปัญหา ดังนั้นฉันจึงไปข้างหน้าและทำสิ่งนี้:

int main(){

    int test = 2;
    clock_t launch = clock();

    test << 6;
    test << 6;
    test << 6;
    test << 6;
    //.... 1 million times
    test << 6;

    clock_t done = clock();
    printf("Time taken : %d\n", done - launch);
    return 0;
}

และคุณก็มีผลลัพธ์ของคุณ

1 ล้านกะการทำงานในเวลาไม่ถึง 1 ไมล์ .

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

ผลการดำเนินงาน Shiftwise

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