เหตุใด GCC จึงสร้างแอสเซมบลีที่ต่างกันอย่างสิ้นเชิงสำหรับรหัส C เกือบเหมือนกัน


184

ในขณะที่เขียนเพิ่มประสิทธิภาพftolการทำงานของผมพบว่าพฤติกรรมแปลก ๆ GCC 4.6.1บางอย่างใน ให้ฉันแสดงรหัสก่อน (เพื่อความชัดเจนฉันได้ทำเครื่องหมายความแตกต่าง):

fast_trunc_one, C:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = mantissa << -exponent;                       /* diff */
    } else {
        r = mantissa >> exponent;                        /* diff */
    }

    return (r ^ -sign) + sign;                           /* diff */
}

fast_trunc_two, C:

int fast_trunc_two(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = (mantissa << -exponent) ^ -sign;             /* diff */
    } else {
        r = (mantissa >> exponent) ^ -sign;              /* diff */
    }

    return r + sign;                                     /* diff */
}

ดูเหมือนว่าจะเหมือนกันใช่มั้ย GCC ไม่เห็นด้วย หลังจากคอมไพล์ด้วยgcc -O3 -S -Wall -o test.s test.cนี่คือผลลัพธ์แอสเซมบลี:

fast_trunc_one สร้าง:

_fast_trunc_one:
LFB0:
    .cfi_startproc
    movl    4(%esp), %eax
    movl    $150, %ecx
    movl    %eax, %edx
    andl    $8388607, %edx
    sarl    $23, %eax
    orl $8388608, %edx
    andl    $255, %eax
    subl    %eax, %ecx
    movl    %edx, %eax
    sarl    %cl, %eax
    testl   %ecx, %ecx
    js  L5
    rep
    ret
    .p2align 4,,7
L5:
    negl    %ecx
    movl    %edx, %eax
    sall    %cl, %eax
    ret
    .cfi_endproc

fast_trunc_two สร้าง:

_fast_trunc_two:
LFB1:
    .cfi_startproc
    pushl   %ebx
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    movl    8(%esp), %eax
    movl    $150, %ecx
    movl    %eax, %ebx
    movl    %eax, %edx
    sarl    $23, %ebx
    andl    $8388607, %edx
    andl    $255, %ebx
    orl $8388608, %edx
    andl    $-2147483648, %eax
    subl    %ebx, %ecx
    js  L9
    sarl    %cl, %edx
    movl    %eax, %ecx
    negl    %ecx
    xorl    %ecx, %edx
    addl    %edx, %eax
    popl    %ebx
    .cfi_remember_state
    .cfi_def_cfa_offset 4
    .cfi_restore 3
    ret
    .p2align 4,,7
L9:
    .cfi_restore_state
    negl    %ecx
    sall    %cl, %edx
    movl    %eax, %ecx
    negl    %ecx
    xorl    %ecx, %edx
    addl    %edx, %eax
    popl    %ebx
    .cfi_restore 3
    .cfi_def_cfa_offset 4
    ret
    .cfi_endproc

นั่นคือความแตกต่างอย่างมาก นี้จริงปรากฏขึ้นบนรายละเอียดมากเกินไปfast_trunc_oneคือประมาณ 30% fast_trunc_twoเร็วกว่า ทีนี้คำถามของฉัน: อะไรทำให้เกิดสิ่งนี้


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

12
วางกรณีทดสอบในไดเรกทอรีของตนเอง -S -O3 -da -fdump-tree-allรวบรวมพวกเขาด้วย สิ่งนี้จะสร้างสแน็ปช็อตจำนวนมากของการนำเสนอระดับกลาง ดำเนินการผ่าน (หมายเลขเหล่านี้) เคียงข้างกันและคุณควรจะสามารถหาการเพิ่มประสิทธิภาพที่ขาดหายไปในกรณีแรก
zwol

1
ข้อเสนอแนะที่สอง: เปลี่ยนทั้งหมดintเป็นunsigned intและดูว่าความแตกต่างนั้นหายไปหรือไม่
zwol

5
ดูเหมือนว่าทั้งสองฟังก์ชั่นกำลังทำคณิตศาสตร์แตกต่างกันเล็กน้อย ขณะที่ผลอาจจะเหมือนกันแสดงออกไม่ได้เช่นเดียวกับ(r + shifted) ^ sign r + (shifted ^ sign)ฉันเดาว่าเครื่องมือเพิ่มประสิทธิภาพสับสนหรือไม่ FWIW, MSVC 2010 (16.00.40219.01) สร้างรายชื่อที่เกือบจะเหมือนกัน: gist.github.com/2430454
DCoder

1
@DCoder: โอ้เจ้ากรรม! ฉันไม่ได้เห็นสิ่งนั้น มันไม่ใช่คำอธิบายสำหรับความแตกต่าง ให้ฉันอัปเดตคำถามด้วยเวอร์ชันใหม่ที่มีการตัดออก
orlp

คำตอบ:


256

อัปเดตเพื่อซิงค์กับการแก้ไขของ OP

ด้วยการแก้ไขรหัสฉันได้จัดการเพื่อดูว่า GCC เพิ่มประสิทธิภาพกรณีและปัญหาแรกอย่างไร

ก่อนที่เราจะเข้าใจว่าทำไมจึงมีความแตกต่างกันอย่างแรกเราต้องเข้าใจว่า GCC เพิ่มประสิทธิภาพfast_trunc_one()ได้อย่างไร

เชื่อหรือไม่ว่าfast_trunc_one()กำลังถูกปรับให้เหมาะกับสิ่งนี้:

int fast_trunc_one(int i) {
    int mantissa, exponent;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);

    if (exponent < 0) {
        return (mantissa << -exponent);             /* diff */
    } else {
        return (mantissa >> exponent);              /* diff */
    }
}

สิ่งนี้จะสร้างแอสเซมบลีที่แน่นอนเหมือนกับต้นฉบับfast_trunc_one()- ชื่อการลงทะเบียนและทุกสิ่ง

แจ้งให้ทราบว่าไม่มีในการชุมนุมสำหรับxor fast_trunc_one()นั่นคือสิ่งที่มอบให้ฉัน


งั้นเหรอ


ขั้นตอนที่ 1: sign = -sign

ก่อนอื่นเรามาดูsignตัวแปร เนื่องจากsign = i & 0x80000000;มีเพียงสองค่าที่เป็นไปได้ที่signสามารถรับได้:

  • sign = 0
  • sign = 0x80000000

ตอนนี้รับรู้ว่าในทั้งสองกรณีsign == -sign. ดังนั้นเมื่อฉันเปลี่ยนรหัสเดิมเป็น:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = mantissa << -exponent;
    } else {
        r = mantissa >> exponent;
    }

    return (r ^ sign) + sign;
}

fast_trunc_one()มันก่อให้เกิดการชุมนุมเดียวกันแน่นอนเช่นเดิม ฉันจะว่างคุณประกอบ แต่มันเหมือนกัน - ลงทะเบียนชื่อและทั้งหมด


ขั้นตอนที่ 2:การลดทางคณิตศาสตร์:x + (y ^ x) = y

signเท่านั้นที่สามารถใช้เวลาหนึ่งในสองค่าหรือ00x80000000

  • เมื่อไหร่x = 0แล้วx + (y ^ x) = yก็เกรียมเล็กน้อย
  • การเพิ่มและ xoring โดย0x80000000เหมือนกัน มันพลิกสัญญาณบิต ดังนั้นยังถือเมื่อx + (y ^ x) = yx = 0x80000000

ดังนั้นเพื่อลดx + (y ^ x) yและรหัสทำให้สิ่งนี้ง่ายขึ้น:

int fast_trunc_one(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = (mantissa << -exponent);
    } else {
        r = (mantissa >> exponent);
    }

    return r;
}

อีกครั้งนี้จะรวบรวมแอสเซมบลีเดียวกันที่แน่นอน - ลงทะเบียนชื่อและทั้งหมด


เวอร์ชันด้านบนนี้ลดลงในที่สุด:

int fast_trunc_one(int i) {
    int mantissa, exponent;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);

    if (exponent < 0) {
        return (mantissa << -exponent);             /* diff */
    } else {
        return (mantissa >> exponent);              /* diff */
    }
}

ซึ่งเป็นสิ่งที่ GCC สร้างขึ้นในแอสเซมบลี


ดังนั้นทำไมคอมไพเลอร์ไม่ปรับfast_trunc_two()ให้เหมาะกับสิ่งเดียวกัน?

ส่วนสำคัญในfast_trunc_one()การx + (y ^ x) = yเพิ่มประสิทธิภาพคือ ในfast_trunc_two()การx + (y ^ x)แสดงออกจะถูกแบ่งข้ามสาขา

ฉันสงสัยว่าอาจจะเพียงพอที่จะทำให้ GCC สับสนเพื่อไม่ทำการเพิ่มประสิทธิภาพนี้ (มันจะต้องยก^ -signออกจากกิ่งไม้และรวมมันเข้าไปr + signในตอนท้าย)

ตัวอย่างเช่นสิ่งนี้สร้างชุดประกอบเหมือนกับfast_trunc_one():

int fast_trunc_two(int i) {
    int mantissa, exponent, sign, r;

    mantissa = (i & 0x07fffff) | 0x800000;
    exponent = 150 - ((i >> 23) & 0xff);
    sign = i & 0x80000000;

    if (exponent < 0) {
        r = ((mantissa << -exponent) ^ -sign) + sign;             /* diff */
    } else {
        r = ((mantissa >> exponent) ^ -sign) + sign;              /* diff */
    }

    return r;                                     /* diff */
}

4
แก้ไขดูเหมือนว่าฉันได้ตอบแก้ไขสอง การแก้ไขปัจจุบันพลิกตัวอย่างทั้งสองและเปลี่ยนรหัสเล็กน้อย ... นี่คือความสับสน
Mysticial

2
@ nightcracker ไม่ต้องกังวล ฉันได้อัปเดตคำตอบเพื่อซิงค์กับเวอร์ชันปัจจุบันแล้ว
Mysticial

1
@Mysticial: คำสั่งสุดท้ายของคุณไม่เป็นความจริงอีกต่อไปแล้วกับเวอร์ชันใหม่ทำให้คำตอบของคุณเป็นโมฆะ (ไม่ตอบคำถามที่สำคัญที่สุด"ทำไม GCC สร้างแอสเซมบลีที่แตกต่างกันอย่างรุนแรง" )
orlp

11
ปรับปรุงคำตอบอีกครั้ง ฉันไม่แน่ใจว่ามันน่าพอใจเพียงพอหรือไม่ แต่ฉันไม่คิดว่าฉันจะสามารถทำได้ดีกว่านี้โดยไม่ทราบว่าการเพิ่มประสิทธิภาพ GCC ที่เกี่ยวข้องดำเนินไปอย่างไร
Mysticial

4
@Mysticial: การพูดอย่างเคร่งครัดตราบใดที่มีการเซ็นชื่อแบบผิด ๆ ถูกใช้ในโค้ดนี้การแปลงทั้งหมดที่คอมไพเลอร์กำลังทำอยู่นี่ในกรณีที่พฤติกรรมไม่ได้กำหนด ...
R .. GitHub STOP ช่วย ICE

63

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

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

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

คุณดูที่ gcc รุ่นต่างๆกันไหม? 3.x และ 4.x โดยเฉพาะ 4.5 ​​vs 4.6 vs 4.7, etc? และสำหรับโปรเซสเซอร์เป้าหมายที่แตกต่างกัน, x86, arm, mips, ฯลฯ หรือรสชาติที่แตกต่างกันของ x86 หากเป็นคอมไพเลอร์ดั้งเดิมที่คุณใช้ 32 บิตเทียบกับ 64 บิต ฯลฯ แล้ว llvm (เสียงดังกราว) สำหรับเป้าหมายที่แตกต่างกันอย่างไร

Mystical ทำงานได้อย่างยอดเยี่ยมในกระบวนการคิดที่ต้องทำงานผ่านปัญหาของการวิเคราะห์ / การเพิ่มประสิทธิภาพโค้ดโดยคาดว่าคอมไพเลอร์จะเกิดขึ้นกับสิ่งใดสิ่งหนึ่งนั่นคือไม่คาดหวังว่าจะมี "คอมไพเลอร์สมัยใหม่"

โดยไม่ต้องเข้าไปในคุณสมบัติทางคณิตศาสตร์รหัสของแบบฟอร์มนี้

if (exponent < 0) {
  r = mantissa << -exponent;                       /* diff */
} else {
  r = mantissa >> exponent;                        /* diff */
}
return (r ^ -sign) + sign;                           /* diff */

กำลังจะนำคอมไพเลอร์ไปที่ A: ใช้มันในรูปแบบนั้นดำเนินการ if-then-else จากนั้นมาบรรจบกับรหัสทั่วไปเพื่อให้เสร็จสิ้นและกลับมา หรือ B: บันทึกสาขาเนื่องจากนี่คือส่วนท้ายของฟังก์ชั่น ไม่ต้องกังวลกับการใช้หรือการบันทึก r

if (exponent < 0) {
  return((mantissa << -exponent)^-sign)+sign;
} else {
  return((mantissa << -exponent)^-sign)+sign;
}

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

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

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

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

for(ra=0;ra<20;ra++) dummy(ra);

มันผลิตที่ใดก็ได้ระหว่างแอสเซมเบลอร์ 60-100 บรรทัด มันปลดลูป ฉันไม่ได้นับบรรทัดถ้าคุณคิดเกี่ยวกับมันต้องเพิ่มคัดลอกผลลัพธ์ไปยังอินพุตไปยังการเรียกใช้ฟังก์ชันทำการเรียกใช้ฟังก์ชันการดำเนินการขั้นต่ำสามครั้ง ดังนั้นขึ้นอยู่กับเป้าหมายที่อาจมีคำสั่งอย่างน้อย 60 คำ, 80 ถ้าสี่ต่อลูป, 100 ถ้าห้าต่อลูป ฯลฯ


ทำไมคุณถึงทำลายคำตอบของคุณ? ดูเหมือนว่า Oded จะไม่เห็นด้วยกับการแก้ไขเช่นกัน ;-)
Peter - Reinstate Monica

@ PeterA.Schneider คำตอบทั้งหมดของเขาดูเหมือนจะถูกทำลายในวันเดียวกัน ฉันคิดว่าคนที่มีข้อมูลบัญชีของเขาถูกขโมย
trinity420

23

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

clangตัวอย่างคอมไพเลอร์ของ LLVM ให้โค้ดเดียวกันสำหรับทั้งสองฟังก์ชัน (ยกเว้นชื่อฟังก์ชัน) โดยให้:

_fast_trunc_two:                        ## @fast_trunc_one
        movl    %edi, %edx
        andl    $-2147483648, %edx      ## imm = 0xFFFFFFFF80000000
        movl    %edi, %esi
        andl    $8388607, %esi          ## imm = 0x7FFFFF
        orl     $8388608, %esi          ## imm = 0x800000
        shrl    $23, %edi
        movzbl  %dil, %eax
        movl    $150, %ecx
        subl    %eax, %ecx
        js      LBB0_1
        shrl    %cl, %esi
        jmp     LBB0_3
LBB0_1:                                 ## %if.then
        negl    %ecx
        shll    %cl, %esi
LBB0_3:                                 ## %if.end
        movl    %edx, %eax
        negl    %eax
        xorl    %esi, %eax
        addl    %edx, %eax
        ret

รหัสนี้ไม่สั้นเท่ากับรุ่น gcc รุ่นแรกจาก OP แต่จะไม่สั้นเท่ากับรุ่นที่สอง

โค้ดจากคอมไพเลอร์อื่น (ซึ่งฉันจะไม่ตั้งชื่อ), คอมไพล์สำหรับ x86_64, สร้างสิ่งนี้สำหรับทั้งสองฟังก์ชั่น:

fast_trunc_one:
        movl      %edi, %ecx        
        shrl      $23, %ecx         
        movl      %edi, %eax        
        movzbl    %cl, %edx         
        andl      $8388607, %eax    
        negl      %edx              
        orl       $8388608, %eax    
        addl      $150, %edx        
        movl      %eax, %esi        
        movl      %edx, %ecx        
        andl      $-2147483648, %edi
        negl      %ecx              
        movl      %edi, %r8d        
        shll      %cl, %esi         
        negl      %r8d              
        movl      %edx, %ecx        
        shrl      %cl, %eax         
        testl     %edx, %edx        
        cmovl     %esi, %eax        
        xorl      %r8d, %eax        
        addl      %edi, %eax        
        ret                         

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

คอมไพเลอร์ Open64 สร้างรายการต่อไปนี้:

fast_trunc_one: 
    movl %edi,%r9d                  
    sarl $23,%r9d                   
    movzbl %r9b,%r9d                
    addl $-150,%r9d                 
    movl %edi,%eax                  
    movl %r9d,%r8d                  
    andl $8388607,%eax              
    negl %r8d                       
    orl $8388608,%eax               
    testl %r8d,%r8d                 
    jl .LBB2_fast_trunc_one         
    movl %r8d,%ecx                  
    movl %eax,%edx                  
    sarl %cl,%edx                   
.Lt_0_1538:
    andl $-2147483648,%edi          
    movl %edi,%eax                  
    negl %eax                       
    xorl %edx,%eax                  
    addl %edi,%eax                  
    ret                             
    .p2align 5,,31
.LBB2_fast_trunc_one:
    movl %r9d,%ecx                  
    movl %eax,%edx                  
    shll %cl,%edx                   
    jmp .Lt_0_1538                  

และที่คล้ายกัน fast_trunc_twoแต่ไม่เหมือนกันสำหรับ

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


10
คอมไพเลอร์ที่คุณจะไม่ตั้งชื่อซูเปอร์คอมพิวเตอร์ลับสุดยอดหรือไม่?
orlp

4
คอมไพเลอร์ความลับสุดยอดน่าจะเป็น iccIntel ฉันมีตัวแปร 32 บิตเท่านั้น แต่ก็สร้างโค้ดที่คล้ายกันมาก
Janus Troelsen

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