'สลับ' เร็วกว่า 'ถ้า' หรือไม่


242

เป็นswitchคำสั่งจริงเร็วกว่าifคำสั่ง?

ฉันรันโค้ดด้านล่างในคอมไพเลอร์ x64 C ++ ของ Visual Studio 2010 ด้วย/Oxค่าสถานะ:

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

#define MAX_COUNT (1 << 29)
size_t counter = 0;

size_t testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        switch (counter % 4 + 1)
        {
            case 1: counter += 4; break;
            case 2: counter += 3; break;
            case 3: counter += 2; break;
            case 4: counter += 1; break;
        }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

size_t testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = counter % 4 + 1;
        if (c == 1) { counter += 4; }
        else if (c == 2) { counter += 3; }
        else if (c == 3) { counter += 2; }
        else if (c == 4) { counter += 1; }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    printf("Starting...\n");
    printf("Switch statement: %u ms\n", testSwitch());
    printf("If     statement: %u ms\n", testIf());
}

และได้รับผลลัพธ์เหล่านี้:

คำสั่งเปลี่ยน: 5261 ms
หากคำสั่ง: 5196 ms

จากสิ่งที่ฉันได้เรียนรู้switchข้อความที่ชัดเจนใช้ตารางข้ามเพื่อเพิ่มประสิทธิภาพการแยก

คำถาม:

  1. ตารางกระโดดพื้นฐานมีหน้าตาเป็นอย่างไรใน x86 หรือ x64

  2. รหัสนี้ใช้ตารางกระโดดหรือไม่?

  3. เหตุใดจึงไม่มีประสิทธิภาพที่แตกต่างในตัวอย่างนี้ จะมีสถานการณ์ใด ๆ ที่มีคือความแตกต่างอย่างมีนัยสำคัญประสิทธิภาพ?


การถอดรหัส:

testIf:

13FE81B10 sub  rsp,48h 
13FE81B14 call qword ptr [__imp_clock (13FE81128h)] 
13FE81B1A mov  dword ptr [start],eax 
13FE81B1E mov  qword ptr [i],0 
13FE81B27 jmp  testIf+26h (13FE81B36h) 
13FE81B29 mov  rax,qword ptr [i] 
13FE81B2E inc  rax  
13FE81B31 mov  qword ptr [i],rax 
13FE81B36 cmp  qword ptr [i],20000000h 
13FE81B3F jae  testIf+0C3h (13FE81BD3h) 
13FE81B45 xor  edx,edx 
13FE81B47 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B4E mov  ecx,4 
13FE81B53 div  rax,rcx 
13FE81B56 mov  rax,rdx 
13FE81B59 inc  rax  
13FE81B5C mov  qword ptr [c],rax 
13FE81B61 cmp  qword ptr [c],1 
13FE81B67 jne  testIf+6Dh (13FE81B7Dh) 
13FE81B69 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B70 add  rax,4 
13FE81B74 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B7B jmp  testIf+0BEh (13FE81BCEh) 
13FE81B7D cmp  qword ptr [c],2 
13FE81B83 jne  testIf+89h (13FE81B99h) 
13FE81B85 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B8C add  rax,3 
13FE81B90 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B97 jmp  testIf+0BEh (13FE81BCEh) 
13FE81B99 cmp  qword ptr [c],3 
13FE81B9F jne  testIf+0A5h (13FE81BB5h) 
13FE81BA1 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BA8 add  rax,2 
13FE81BAC mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BB3 jmp  testIf+0BEh (13FE81BCEh) 
13FE81BB5 cmp  qword ptr [c],4 
13FE81BBB jne  testIf+0BEh (13FE81BCEh) 
13FE81BBD mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BC4 inc  rax  
13FE81BC7 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BCE jmp  testIf+19h (13FE81B29h) 
13FE81BD3 call qword ptr [__imp_clock (13FE81128h)] 
13FE81BD9 sub  eax,dword ptr [start] 
13FE81BDD imul eax,eax,3E8h 
13FE81BE3 cdq       
13FE81BE4 mov  ecx,3E8h 
13FE81BE9 idiv eax,ecx 
13FE81BEB cdqe      
13FE81BED add  rsp,48h 
13FE81BF1 ret       

testSwitch:

13FE81C00 sub  rsp,48h 
13FE81C04 call qword ptr [__imp_clock (13FE81128h)] 
13FE81C0A mov  dword ptr [start],eax 
13FE81C0E mov  qword ptr [i],0 
13FE81C17 jmp  testSwitch+26h (13FE81C26h) 
13FE81C19 mov  rax,qword ptr [i] 
13FE81C1E inc  rax  
13FE81C21 mov  qword ptr [i],rax 
13FE81C26 cmp  qword ptr [i],20000000h 
13FE81C2F jae  testSwitch+0C5h (13FE81CC5h) 
13FE81C35 xor  edx,edx 
13FE81C37 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C3E mov  ecx,4 
13FE81C43 div  rax,rcx 
13FE81C46 mov  rax,rdx 
13FE81C49 inc  rax  
13FE81C4C mov  qword ptr [rsp+30h],rax 
13FE81C51 cmp  qword ptr [rsp+30h],1 
13FE81C57 je   testSwitch+73h (13FE81C73h) 
13FE81C59 cmp  qword ptr [rsp+30h],2 
13FE81C5F je   testSwitch+87h (13FE81C87h) 
13FE81C61 cmp  qword ptr [rsp+30h],3 
13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
13FE81C69 cmp  qword ptr [rsp+30h],4 
13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 
13FE81C71 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C73 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C7A add  rax,4 
13FE81C7E mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C85 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C87 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C8E add  rax,3 
13FE81C92 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C99 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C9B mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CA2 add  rax,2 
13FE81CA6 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CAD jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81CAF mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CB6 inc  rax  
13FE81CB9 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CC0 jmp  testSwitch+19h (13FE81C19h) 
13FE81CC5 call qword ptr [__imp_clock (13FE81128h)] 
13FE81CCB sub  eax,dword ptr [start] 
13FE81CCF imul eax,eax,3E8h 
13FE81CD5 cdq       
13FE81CD6 mov  ecx,3E8h 
13FE81CDB idiv eax,ecx 
13FE81CDD cdqe      
13FE81CDF add  rsp,48h 
13FE81CE3 ret       

ปรับปรุง:

ผลลัพธ์ที่น่าสนใจที่นี่ ไม่แน่ใจว่าทำไมจึงเร็วกว่าและช้ากว่า


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

6
เกิดอะไรขึ้นกับคำถามนี้
Tugrul Ates

25
ให้ทุกคนที่สงสัยว่ามีอะไรผิดปกติกับคำถามนี้ : สำหรับ starters, มันไม่ได้เป็นคำถามมันเป็น3คำถามซึ่งหมายความว่าจำนวนมากของคำตอบในขณะนี้การแก้ไขปัญหาที่แตกต่างกัน ซึ่งหมายความว่ามันจะเป็นเรื่องยากที่จะยอมรับคำตอบที่ตอบใด ๆทุกอย่าง นอกจากนี้ปฏิกิริยาเข่าเหวี่ยงทั่วไปคำถามข้างต้นคือการปิดว่า "ไม่ได้จริงๆสิ่งที่น่าสนใจ" ส่วนใหญ่เกิดจากความจริงที่ว่าในระดับของการเพิ่มประสิทธิภาพนี้คุณมักจะเพิ่มประสิทธิภาพก่อนเวลาอันควร สุดท้ายนี้ 5196 กับ 5261 ไม่น่าจะเพียงพอที่จะใส่ใจ เขียนโค้ดโลจิคัลที่สมเหตุสมผล
Lasse V. Karlsen

40
@Lasse: คุณจะจริงๆได้แนะนำฉันไปโพสต์ที่สามคำถามเกี่ยวกับ SO แทน? นอกจากนี้: 5196 vs. 5261 shouldn't be enough to actually care-> ฉันไม่แน่ใจว่าคุณเข้าใจผิดคำถามหรือถ้าฉันเข้าใจผิดความคิดเห็นของคุณ แต่ไม่ใช่คำถามทั้งหมดของฉันที่จะถามว่าทำไมไม่มีความแตกต่าง? (ฉันเคยอ้างว่าเป็นความแตกต่างที่สำคัญในการดูแล?)
user541686

5
@Robert: มันมีมากกว่า 20 ความคิดเห็นเพราะมันเป็นความคิดเห็นของเมตา มีเพียง 7 ความคิดเห็นที่เกี่ยวข้องกับคำถามที่นี่จริงๆ ความคิดเห็น: ฉันไม่เห็นว่ามี "ความเห็น" ที่นี่อย่างไร มีเหตุผลที่ฉันไม่เห็นความแตกต่างด้านประสิทธิภาพใช่ไหม มันเป็นแค่รสชาติหรือไม่? อภิปราย: อาจเป็นไปได้ แต่ดูเหมือนว่าฉันจะเป็นคนถกเถียงกันอย่างมีสุขภาพดีเหมือนที่ฉันเคยเห็นในสถานที่อื่นใน SO (แจ้งให้เราทราบหากมีสิ่งใดขัดแย้งกับมัน) อาร์กิวเมนต์: ฉันไม่เห็นสิ่งที่โต้แย้งที่นี่ (เว้นแต่คุณจะใช้มันเป็นคำพ้องสำหรับ 'อภิปราย'?) การอภิปรายเพิ่มเติม: หากคุณรวม meta-comments เหล่านี้
user541686

คำตอบ:


122

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

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

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

ในการตอบคำถามเฉพาะของคุณ:

  1. เสียงดังกราวสร้างหนึ่งที่ดูเหมือน นี้ :

    test_switch(char):                       # @test_switch(char)
            movl    %edi, %eax
            cmpl    $19, %edi
            jbe     .LBB0_1
            retq
    .LBB0_1:
            jmpq    *.LJTI0_0(,%rax,8)
            jmp     void call<0u>()         # TAILCALL
            jmp     void call<1u>()         # TAILCALL
            jmp     void call<2u>()         # TAILCALL
            jmp     void call<3u>()         # TAILCALL
            jmp     void call<4u>()         # TAILCALL
            jmp     void call<5u>()         # TAILCALL
            jmp     void call<6u>()         # TAILCALL
            jmp     void call<7u>()         # TAILCALL
            jmp     void call<8u>()         # TAILCALL
            jmp     void call<9u>()         # TAILCALL
            jmp     void call<10u>()        # TAILCALL
            jmp     void call<11u>()        # TAILCALL
            jmp     void call<12u>()        # TAILCALL
            jmp     void call<13u>()        # TAILCALL
            jmp     void call<14u>()        # TAILCALL
            jmp     void call<15u>()        # TAILCALL
            jmp     void call<16u>()        # TAILCALL
            jmp     void call<17u>()        # TAILCALL
            jmp     void call<18u>()        # TAILCALL
            jmp     void call<19u>()        # TAILCALL
    .LJTI0_0:
            .quad   .LBB0_2
            .quad   .LBB0_3
            .quad   .LBB0_4
            .quad   .LBB0_5
            .quad   .LBB0_6
            .quad   .LBB0_7
            .quad   .LBB0_8
            .quad   .LBB0_9
            .quad   .LBB0_10
            .quad   .LBB0_11
            .quad   .LBB0_12
            .quad   .LBB0_13
            .quad   .LBB0_14
            .quad   .LBB0_15
            .quad   .LBB0_16
            .quad   .LBB0_17
            .quad   .LBB0_18
            .quad   .LBB0_19
            .quad   .LBB0_20
            .quad   .LBB0_21
  2. ฉันสามารถพูดได้ว่ามันไม่ได้ใช้ตารางกระโดด - 4 คำแนะนำการเปรียบเทียบจะมองเห็นได้ชัดเจน:

    13FE81C51 cmp  qword ptr [rsp+30h],1 
    13FE81C57 je   testSwitch+73h (13FE81C73h) 
    13FE81C59 cmp  qword ptr [rsp+30h],2 
    13FE81C5F je   testSwitch+87h (13FE81C87h) 
    13FE81C61 cmp  qword ptr [rsp+30h],3 
    13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
    13FE81C69 cmp  qword ptr [rsp+30h],4 
    13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 

    โซลูชันที่ใช้ตารางข้ามไม่ได้ใช้การเปรียบเทียบเลย

  3. มีสาขาไม่เพียงพอที่จะทำให้คอมไพเลอร์สร้างตารางกระโดดหรือคอมไพเลอร์ของคุณก็ไม่ได้สร้างพวกเขา ฉันไม่แน่ใจ

แก้ไข 2014 : มีการสนทนาจากที่อื่นที่คนคุ้นเคยกับเครื่องมือเพิ่มประสิทธิภาพ LLVM โดยกล่าวว่าการเพิ่มประสิทธิภาพตารางกระโดดมีความสำคัญในหลาย ๆ สถานการณ์ เช่นในกรณีที่มีการแจงนับที่มีค่าจำนวนมากและอีกหลายกรณีเทียบกับค่าในการระบุดังกล่าว ที่กล่าวว่าฉันยืนอยู่กับที่ฉันกล่าวข้างต้นในปี 2011 - บ่อยครั้งที่ฉันเห็นคนคิดว่า "ถ้าฉันทำให้สวิตช์มันจะเป็นเวลาเดียวกันไม่ว่าฉันจะมีกี่กรณี" - และนั่นก็เป็นเรื่องที่ผิดทั้งหมด แม้จะมีตารางกระโดดคุณจะได้รับต้นทุนการกระโดดทางอ้อมและคุณจ่ายเงินสำหรับรายการในตารางสำหรับแต่ละกรณี และแบนด์วิดธ์หน่วยความจำเป็นเรื่องใหญ่สำหรับฮาร์ดแวร์ที่ทันสมัย

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


3
+1 สำหรับการตอบคำถามจริงและสำหรับข้อมูลที่มีประโยชน์ :-) อย่างไรก็ตามคำถาม: จากสิ่งที่ฉันเข้าใจตารางการกระโดดใช้การข้ามทางอ้อม ถูกต้องหรือไม่ ถ้าเป็นเช่นนั้นนั่นมักจะไม่ช้าลงเนื่องจากการดึง / การวางท่อล่วงหน้ายากขึ้นหรือไม่
user541686

1
@Mehrdad: ใช่มันใช้การกระโดดทางอ้อม อย่างไรก็ตามการกระโดดทางอ้อมหนึ่งครั้ง (โดยมีแผงลอยไปป์ไลน์) อาจมีการกระโดดข้ามได้น้อยกว่าหลายร้อยครั้ง :)
Billy ONeal

1
@Mehrdad: ไม่น่าเสียดาย :( ฉันดีใจที่ฉันอยู่ในค่ายของผู้คนที่มักจะคิดว่า IF อ่านง่ายกว่านี้มาก :)
Billy ONeal

1
ไม่กี่ quips - "[สวิทช์] จะทำงานเฉพาะเมื่ออินพุตสามารถถูก จำกัด ขอบเขตด้วยวิธีใดวิธีหนึ่ง" "จำเป็นต้องแทรกรูปแบบบางส่วนของการทดสอบถ้ารอบโต๊ะเพื่อให้แน่ใจว่าอินพุตนั้นถูกต้องในตารางหมายเหตุด้วย กรณีที่อินพุตเป็นตัวเลขที่ต่อเนื่องกัน ": เป็นไปได้อย่างสิ้นเชิงที่จะมีตารางที่มีประชากรเบาบางโดยที่ตัวชี้ที่เป็นไปได้นั้นจะถูกอ่านและเฉพาะในกรณีที่ไม่ใช่ค่า NULL เป็นการดำเนินการกระโดดมิฉะนั้นเป็นกรณีเริ่มต้น จากนั้นswitchทางออก โซเรนพูดหลายสิ่งหลายอย่างที่ฉันอยากจะพูดหลังจากอ่านคำตอบนี้
Tony Delroy

2
"คอมไพเลอร์ใด ๆ ที่มีมูลค่าเกลือจะเห็นว่าถ้า / อื่นถ้าแลดเดอร์และเปลี่ยนเป็นสวิตช์ที่เทียบเท่าหรือในทางกลับกัน" - การสนับสนุนใด ๆ สำหรับการยืนยันนี้? คอมไพเลอร์อาจคิดว่าลำดับของคำสั่งของคุณifได้รับการปรับแต่งด้วยมือเพื่อจับคู่ความถี่และความต้องการประสิทธิภาพสัมพัทธ์ซึ่งเป็นswitchแบบดั้งเดิมที่เห็นว่าเป็นการเชิญแบบเปิดเพื่อเพิ่มประสิทธิภาพอย่างไรก็ตามคอมไพเลอร์เลือก จุดที่ดีอีกครั้งกระโดดผ่านswitch:-) ขนาดรหัสขึ้นอยู่กับกรณี / ช่วง - อาจดีกว่า ในที่สุด enums ฟิลด์บิตและcharสถานการณ์บางอย่างนั้นมีความถูกต้อง / มีขอบเขตและไม่มีค่าใช้จ่าย
Tony Delroy

47

สำหรับคำถามของคุณ:

1. โต๊ะกระโดดพื้นฐานมีหน้าตาเป็นอย่างไรใน x86 หรือ x64?

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

00B14538  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00  Ø.«.Ø.«.Ø.«.Ø.«.
00B14548  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 00 00 00 00  Ø.«.Ø.«.Ø.«.....
00B14558  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00B14568  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

ป้อนคำอธิบายรูปภาพที่นี่

โดยที่00B14538เป็นตัวชี้ไปยังตารางการกระโดดและค่าเช่นD8 09 AB 00หมายถึงตัวชี้ฉลาก

2. รหัสนี้ใช้ตารางข้ามหรือไม่ ไม่ในกรณีนี้

3. ทำไมตัวอย่างนี้จึงไม่มีความแตกต่างด้านประสิทธิภาพ?

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

4. มีสถานการณ์ใดบ้างที่มีความแตกต่างของประสิทธิภาพที่สำคัญ?

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

รหัสสำหรับคำแนะนำในการเปรียบเทียบทั้งหมดมีขนาดบางส่วนเช่นกันโดยเฉพาะอย่างยิ่งเมื่อมีตัวชี้ 32- บิตหรือออฟเซ็ตการค้นหาตารางกระโดดครั้งเดียวอาจไม่ทำให้มีขนาดที่มากขึ้นในการปฏิบัติการ

สรุป: คอมไพเลอร์ฉลาดพอรับมือกับกรณีดังกล่าวและสร้างคำแนะนำที่เหมาะสม :)


(แก้ไข: nvm คำตอบของบิลลี่มีสิ่งที่ฉันแนะนำอยู่แล้วฉันคิดว่านี่เป็นสิ่งที่ดีมาก) มันจะเป็นการดีที่จะรวมgcc -Sเอาท์พุท: ลำดับของรายการ.long L1/ .long L2ตารางมีความหมายมากกว่า hexdump และมีประโยชน์กับคนที่ ต้องการเรียนรู้วิธีดูคอมไพเลอร์ (แม้ว่าฉันเดาว่าคุณแค่ดูรหัสสวิตช์เพื่อดูว่ามันเป็น jmp ทางอ้อมหรือ jcc จำนวนมาก)
Peter Cordes

31

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

ฉันจะเชื่อใจคอมไพเลอร์เพื่อทำทางเลือกที่ดีที่สุดและมุ่งเน้นไปที่สิ่งที่ทำให้โค้ดอ่านได้มากที่สุด

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


13
ฉันไม่คิดว่านี่จะตอบคำถามของ OP เลย
Billy ONeal

5
@ โซเรน: ถ้านั่นเป็น "คำถามพื้นฐาน" ถ้างั้นฉันคงไม่สนใจกับคำถามอื่นอีก 179 บรรทัดมันก็แค่ 1 บรรทัด :-)
user541686

8
@Soren: ฉันเห็นคำถามย่อยอย่างน้อย 3 หมายเลขเป็นส่วนหนึ่งของคำถามของ OP คุณได้ตอบคำถามที่ถูกต้องซึ่งนำไปใช้กับคำถาม "ประสิทธิภาพ" ทั้งหมดแล้วเท่านั้น - กล่าวคือคุณต้องวัดค่าก่อน ลองคิดดูว่าเมห์ร์ดาดอาจวัดแล้วและแยกรหัสนี้เป็นจุดที่น่าสนใจ ในกรณีเช่นนี้คำตอบของคุณแย่กว่าไร้ค่ามันเป็นเสียงรบกวน
Billy ONeal

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

2
@wnoise: ถ้าเป็นคำตอบเดียวที่ถูกต้องก็คงไม่มีเหตุผลที่จะถามคำถามเกี่ยวกับประสิทธิภาพเลย อย่างไรก็ตามมีพวกเราบางคนในโลกแห่งความเป็นจริงที่ทำการวัดซอฟต์แวร์ของเราและบางครั้งเราก็ไม่รู้ว่าจะทำให้ชิ้นส่วนของรหัสเร็วขึ้นได้อย่างไรเมื่อทำการวัดแล้ว เห็นได้ชัดว่าเมห์ดัดใช้ความพยายามในคำถามนี้ก่อนถาม และฉันคิดว่าคำถามเฉพาะของเขาตอบได้ดีกว่า
Billy ONeal

13

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

  1. ความแตกต่างมีขนาดเล็กมาก
  2. มีผลลัพธ์เดียวเท่านั้นไม่ใช่ชุดผลลัพธ์
  3. มีน้อยเกินไป

ผลลัพธ์ของฉัน:

ฉันเพิ่ม:

printf("counter: %u\n", counter);

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

ปัญหาอื่น ๆ เกี่ยวกับรหัสของคุณคือ:

switch (counter % 4 + 1)

ในวงสวิตช์ของคุณกับ

const size_t c = counter % 4 + 1; 

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

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

ฉันได้แก้ไขโค้ดของ Ryan เพียงพอที่จะทำให้แน่ใจว่าคอมไพเลอร์ไม่สามารถคิดออกก่อนที่รหัสจะทำงาน:

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

#define MAX_COUNT (1 << 26)
size_t counter = 0;

long long testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = rand() % 20 + 1;

        switch (c)
        {
                case 1: counter += 20; break;
                case 2: counter += 33; break;
                case 3: counter += 62; break;
                case 4: counter += 15; break;
                case 5: counter += 416; break;
                case 6: counter += 3545; break;
                case 7: counter += 23; break;
                case 8: counter += 81; break;
                case 9: counter += 256; break;
                case 10: counter += 15865; break;
                case 11: counter += 3234; break;
                case 12: counter += 22345; break;
                case 13: counter += 1242; break;
                case 14: counter += 12341; break;
                case 15: counter += 41; break;
                case 16: counter += 34321; break;
                case 17: counter += 232; break;
                case 18: counter += 144231; break;
                case 19: counter += 32; break;
                case 20: counter += 1231; break;
        }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

long long testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = rand() % 20 + 1;
        if (c == 1) { counter += 20; }
        else if (c == 2) { counter += 33; }
        else if (c == 3) { counter += 62; }
        else if (c == 4) { counter += 15; }
        else if (c == 5) { counter += 416; }
        else if (c == 6) { counter += 3545; }
        else if (c == 7) { counter += 23; }
        else if (c == 8) { counter += 81; }
        else if (c == 9) { counter += 256; }
        else if (c == 10) { counter += 15865; }
        else if (c == 11) { counter += 3234; }
        else if (c == 12) { counter += 22345; }
        else if (c == 13) { counter += 1242; }
        else if (c == 14) { counter += 12341; }
        else if (c == 15) { counter += 41; }
        else if (c == 16) { counter += 34321; }
        else if (c == 17) { counter += 232; }
        else if (c == 18) { counter += 144231; }
        else if (c == 19) { counter += 32; }
        else if (c == 20) { counter += 1231; }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    srand(time(NULL));
    printf("Starting...\n");
    printf("Switch statement: %lld ms\n", testSwitch()); fflush(stdout);
    printf("counter: %d\n", counter);
    counter = 0;
    srand(time(NULL));
    printf("If     statement: %lld ms\n", testIf()); fflush(stdout);
    printf("counter: %d\n", counter);
} 

สวิตช์: 3740
ถ้า: 3980

(ผลลัพธ์ที่คล้ายกันมากกว่าความพยายามหลายครั้ง)

ฉันยังลดจำนวนเคส / ifs เป็น 5 และฟังก์ชั่นสวิตช์ยังคงได้รับรางวัล


IDK ฉันไม่สามารถพิสูจน์ได้ คุณได้รับผลลัพธ์ที่แตกต่างกันอย่างไร
user541686

+1: การเปรียบเทียบเป็นเรื่องยากและคุณไม่สามารถสรุปได้จากความแตกต่างของเวลาเล็กน้อยในการทำงานครั้งเดียวบนคอมพิวเตอร์ปกติ คุณอาจลองทำการทดสอบจำนวนมากและทำสถิติเกี่ยวกับผลลัพธ์ หรือการนับรอบโปรเซสเซอร์ในการดำเนินการควบคุมในโปรแกรมจำลอง
โทมัส Padron-McCarthy

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

1
@BobTurbo: 45983493 มากกว่า 12 ชั่วโมง นั่นคือการพิมพ์ผิดหรือเปล่า?
กัส

1
ดีตอนนี้ผมต้องไปทำมันอีกครั้ง :)
BobTurbo

7

คอมไพเลอร์การเพิ่มประสิทธิภาพที่ดีเช่น MSVC สามารถสร้าง:

  1. ตารางการกระโดดง่าย ๆ หากกรณีถูกจัดเรียงในระยะยาวที่ดี
  2. ตารางกระโดดแบบเบาบาง (สองระดับ) หากมีช่องว่างจำนวนมาก
  3. ชุดของ ifs หากจำนวนของคดีมีขนาดเล็กหรือค่าไม่ได้อยู่ใกล้กัน
  4. การรวมกันของด้านบนถ้ากรณีเป็นตัวแทนของกลุ่มหลายช่วงที่มีระยะห่างอย่างใกล้ชิด

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


ที่จริงแล้วคอมไพเลอร์ยังสามารถแทนที่ด้วยแฮชและกระโดดซึ่งทำได้ดีกว่าโซลูชันสองระดับที่คุณเสนอ
อลิซ

5

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


+1 ขอบคุณสำหรับคำตอบที่ # 2! :) (Btw นี่คือผลลัพธ์ที่มีความเป็นไปได้มากขึ้น)
user541686

4

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

หากคุณได้รับ 40 if statement และเพิ่ม 0 case ดังนั้นบล็อก if จะทำงานช้ากว่าคำสั่ง switch เทียบเท่า ฉันมีผลลัพธ์ที่นี่: https://www.ideone.com/KZeCz

ผลของการถอด 0 กรณีที่สามารถมองเห็นได้ที่นี่: https://www.ideone.com/LFnrX


1
ลิงก์ของคุณพัง
TS

4

นี่คือผลการค้นหาบางอย่างจากการเปรียบเทียบ benchmark ++ เก่า (ตอนนี้หายาก):

Test Name:   F000003                         Class Name:  Style
CPU Time:       0.781  nanoseconds           plus or minus     0.0715
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way if/else if statement
 compare this test with F000004

Test Name:   F000004                         Class Name:  Style
CPU Time:        1.53  nanoseconds           plus or minus     0.0767
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way switch statement
 compare this test with F000003

Test Name:   F000005                         Class Name:  Style
CPU Time:        7.70  nanoseconds           plus or minus      0.385
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way if/else if statement
 compare this test with F000006

Test Name:   F000006                         Class Name:  Style
CPU Time:        2.00  nanoseconds           plus or minus     0.0999
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way switch statement
 compare this test with F000005

Test Name:   F000007                         Class Name:  Style
CPU Time:        3.41  nanoseconds           plus or minus      0.171
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way sparse switch statement
 compare this test with F000005 and F000006

สิ่งที่เราเห็นได้จากสิ่งนี้คือ (บนเครื่องนี้ด้วยคอมไพเลอร์นี้ - VC ++ 9.0 x64) ifการทดสอบแต่ละครั้งใช้เวลาประมาณ 0.7 นาโนวินาที เมื่อจำนวนการทดสอบเพิ่มขึ้นเวลาจะปรับตามแนวเส้นตรงเกือบสมบูรณ์แบบ

ด้วยคำสั่ง switch นั้นแทบไม่มีความแตกต่างด้านความเร็วระหว่างการทดสอบแบบสองทางและแบบ 10 ทางตราบใดที่ค่าต่าง ๆ นั้นมีความหนาแน่น การทดสอบ 10 ทิศทางที่มีค่าน้อยมากใช้เวลาประมาณ 1.6 เท่าของการทดสอบ 10 ทิศทางด้วยค่าความหนาแน่นสูง แต่ถึงแม้จะมีค่าระยะห่าง แต่ก็ยังดีกว่าความเร็ว 10 เท่าif/ 10 เท่าelse ifเท่า

บรรทัดด้านล่าง: โดยใช้เพียงการทดสอบ 4 ทิศทางจะไม่จริงแสดงให้คุณมากเกี่ยวกับการปฏิบัติงานของswitchVS /if elseหากคุณมองไปที่ตัวเลขจากรหัสนี้ก็สวยง่ายที่จะสอดแทรกความจริงที่ว่าสำหรับการทดสอบ 4 ทิศทาง, เราคาดหวังว่าทั้งสองในการผลิตสวยผลที่คล้ายกัน (~ 2.8 นาโนวินาทีสำหรับif/ else~ 2.0 switch)


1
ยากที่จะรู้ว่าจะทำอย่างไรถ้าเราไม่รู้ว่าการทดสอบนั้นหาค่าที่ไม่ตรงกันหรือจับคู่ที่ส่วนท้ายของห่วงโซ่if/ elseเทียบกับการกระเจิงพวกมัน ฯลฯ ไม่สามารถหาbench++แหล่งที่มาหลังจาก 10 นาที googling
Tony Delroy

3

โปรดทราบว่าเมื่อสวิทช์ไม่ได้รวบรวมไปยังตารางการกระโดดคุณมักจะสามารถเขียนว่ามีประสิทธิภาพมากกว่าสวิทช์ ...

(1) หากกรณีมีการสั่งซื้อมากกว่าการทดสอบกรณีที่เลวร้ายที่สุดสำหรับ N ทั้งหมดคุณสามารถเขียนของคุณถ้าจะทดสอบว่าในครึ่งบนหรือล่างจากนั้นในแต่ละครึ่งของรูปแบบการค้นหาแบบไบนารี ... ส่งผลให้ กรณีที่แย่ที่สุดคือ logN แทนที่จะเป็น N

(2) หากบางกรณี / กลุ่มอยู่บ่อยกว่ากรณีอื่น ๆ ดังนั้นการออกแบบกรณีที่จะแยกกรณีเหล่านั้นก่อนสามารถเร่งเวลาเฉลี่ยผ่าน


สิ่งนี้ไม่จริงอย่างชัดเจน คอมไพเลอร์มีมากกว่าความสามารถในการเพิ่มประสิทธิภาพทั้งสองนี้
อลิซ

1
อลิซคอมไพเลอร์ควรจะรู้ได้อย่างไรว่ากรณีใดจะเกิดขึ้นบ่อยกว่ากรณีอื่น ๆ ในปริมาณงานที่คุณคาดหวัง (A: มันเป็นไปไม่ได้ที่จะรู้ดังนั้นจึงไม่สามารถปรับปรุงให้เหมาะสมได้)
Brian Kennedy

(1) สามารถทำได้ง่ายและทำได้ในคอมไพเลอร์บางตัวโดยทำการค้นหาแบบไบนารี่ (2) สามารถทำนายได้หลายวิธีหรือสามารถระบุได้กับคอมไพเลอร์ คุณไม่เคยใช้ "โอกาส" หรือ GCC "ไม่น่าเป็นไปได้" หรือไม่?
อลิซ

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

2

ไม่ใช่สิ่งเหล่านี้ถ้าเป็นอย่างนั้นก็ให้ข้ามไปถ้าอย่างนั้นก็ข้าม ... ตารางกระโดดจะมีตารางที่อยู่หรือใช้แฮชหรืออะไรทำนองนั้น

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

หากคุณใช้กรณี 0-3 แทนที่จะเป็น 1-4 คอมไพเลอร์อาจใช้ตารางการกระโดดคอมไพเลอร์ควรจะคิดลบ +1 ของคุณต่อไป บางทีมันอาจเป็นจำนวนน้อย ถ้าคุณทำมัน 0 - 15 หรือ 0 - 31 ตัวอย่างเช่นมันอาจจะนำไปใช้กับตารางหรือใช้ทางลัดอื่น ๆ คอมไพเลอร์มีอิสระที่จะเลือกว่าจะใช้สิ่งต่าง ๆ อย่างไรตราบใดที่มันสอดคล้องกับการทำงานของซอร์สโค้ด และสิ่งนี้ทำให้เกิดความแตกต่างของคอมไพเลอร์และความแตกต่างของรุ่นและความแตกต่างของการปรับให้เหมาะสม หากคุณต้องการตารางกระโดดให้สร้างตารางข้ามหากคุณต้องการต้นไม้ if-then-else ทำต้นไม้ if-then-else หากคุณต้องการให้คอมไพเลอร์ตัดสินใจให้ใช้คำสั่ง switch / case


2

ไม่แน่ใจว่าทำไมจึงเร็วกว่าและช้ากว่า

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

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

ใน % 21เวอร์ชั่นกิ่งก้านจะเป็นแบบสุ่ม ดังนั้นไม่เพียง แต่พวกเขาหลายคนจะทำซ้ำทุกครั้ง CPU ไม่สามารถเดาได้ว่าจะไปทางไหน นี่เป็นกรณีที่ตารางการข้าม (หรือการเพิ่มประสิทธิภาพ "สวิตช์" อื่น ๆ ) น่าจะช่วยได้

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

ทั้งหมดนี้คือการบอกว่าคำอธิบายข้างต้นของฉันเป็นการคาดเดา :-)


2
ฉันไม่เห็นว่าที่มาของช้าลงหลายร้อยเท่า กรณีที่เลวร้ายที่สุดของสาขาที่มีการป้องกันผิดคือแผงขายน้ำมันซึ่งจะช้ากว่าซีพียูสมัยใหม่ส่วนใหญ่ประมาณ 20 เท่า ไม่ใช่หลายร้อยเท่า (โอเคถ้าคุณใช้ชิป NetBurst เก่ามันอาจจะช้าลง 35x ... )
Billy ONeal

@Billy: ตกลงดังนั้นฉันมองไปข้างหน้าเล็กน้อย สำหรับโปรเซสเซอร์ Sandy Bridge "แต่ละสาขาที่มีการป้องกันผิดจะล้างท่อทั้งหมดทำให้สูญเสียงานถึงหนึ่งร้อยหรือมากกว่านั้นในคำแนะนำในการบิน" ท่อทำจริงๆได้รับลึกกับทุกรุ่นโดยทั่วไป ...
Nemo

1
ไม่จริง. P4 (NetBurst) มี 31 ขั้นตอนไปป์ไลน์ Sandy Bridge มีสเตจที่น้อยลงอย่างมาก ฉันคิดว่า "การสูญเสียงาน 100 คำสั่ง" นั้นอยู่ภายใต้การสันนิษฐานว่าแคชคำสั่งนั้นไม่ถูกต้อง สำหรับการกระโดดทางอ้อมทั่วไปที่เกิดขึ้นจริง แต่สำหรับบางอย่างเช่นตารางการกระโดดมีแนวโน้มว่าเป้าหมายของการกระโดดทางอ้อมจะอยู่ที่ใดที่หนึ่งในแคชคำสั่ง
Billy ONeal

@Billy: ฉันไม่คิดว่าเราไม่เห็นด้วย คำสั่งของฉันคือ: "สาขาที่ผิดพลาดมีราคาแพงกว่าสาขาที่คาดการณ์อย่างถูกต้องหลายสิบถึงร้อยเท่า" การพูดเกินจริงเล็กน้อยบางที ... แต่มีมากกว่าที่เกิดขึ้นเพียงแค่ความนิยมในความลึกแคชและการดำเนินการ จากสิ่งที่ฉันได้อ่านคิวสำหรับการถอดรหัสเพียงอย่างเดียวคือคำสั่ง ~ 20
Nemo

หากฮาร์ดแวร์การคาดคะเนสาขาคาดเดาเส้นทางการเรียกใช้งานไม่ถูกต้อง uops จากเส้นทางที่ไม่ถูกต้องซึ่งอยู่ในขั้นตอนการสอนจะถูกลบออกโดยที่ไม่ต้องดำเนินการใด ๆ ฉันไม่รู้ว่ามันเป็นไปได้อย่างไร (หรือว่าฉันตีความมันผิด) แต่เห็นได้ชัดว่าไม่มีแผงลอยไปป์ไลน์ที่มีสาขาที่ผิดใน Nehalem? (จากนั้นอีกครั้งฉันไม่มี i7 ฉันมี i5 ดังนั้นนี่จึงไม่สามารถใช้ได้กับกรณีของฉัน)
user541686

1

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

counter += (4 - counter % 4);

ดูเหมือนจะเป็นนิพจน์ส่วนเพิ่มที่ถูกต้องที่คุณควรใช้

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