2013-02-09 37 views
11

Chỉ GCC manual chỉ hiển thị các ví dụ trong đó __builtin_expect() được đặt xung quanh toàn bộ điều kiện của câu lệnh 'if'. Tôi cũng nhận thấy rằng GCC không phàn nàn nếu tôi sử dụng nó, ví dụ, với một toán tử bậc ba, hoặc trong bất kỳ biểu thức tích phân tùy ý nào cho vấn đề đó, thậm chí là một biểu thức không được sử dụng trong ngữ cảnh phân nhánh.Tôi có thể sử dụng hàm __builtin_expect() của GCC với toán tử bậc ba trong C

Vì vậy, tôi tự hỏi những hạn chế cơ bản của việc sử dụng nó thực sự là gì.

Nó sẽ giữ lại ảnh hưởng của nó khi được sử dụng trong một hoạt động ternary như thế này:

int foo(int i) 
{ 
    return __builtin_expect(i == 7, 1) ? 100 : 200; 
} 

Và những gì về trường hợp này:

int foo(int i) 
{ 
    return __builtin_expect(i, 7) == 7 ? 100 : 200; 
} 

Và một điều này:

int foo(int i) 
{ 
    int j = __builtin_expect(i, 7); 
    return j == 7 ? 100 : 200; 
} 

Trả lời

8

Nó dường như hoạt động cho cả hai câu lệnh nếu có.

Trước tiên, chúng ta hãy xem xét ba mẫu mã sau đây, hai trong số đó sử dụng __builtin_expect trong cả hai kiểu thường-nếu và ba giây nếu và thứ ba không sử dụng nó.

builtin.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal; 
    if (__builtin_expect(c == 'c', 1)) 
    { 
     printVal = "Took expected branch!\n"; 
    } 
    else 
    { 
     printVal = "Boo!\n"; 
    } 

    printf(printVal); 
} 

ternary.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal = __builtin_expect(c == 'c', 1) 
     ? "Took expected branch!\n" 
     : "Boo!\n"; 

    printf(printVal); 
} 

nobuiltin.c:

int main() 
{ 
    char c = getchar(); 
    const char *printVal; 
    if (c == 'c') 
    { 
     printVal = "Took expected branch!\n"; 
    } 
    else 
    { 
     printVal = "Boo!\n"; 
    } 

    printf(printVal); 
} 

Khi biên soạn với -O3, cả ba kết quả trong cùng một assembly. Tuy nhiên, khi -O được bỏ qua (trên GCC 4.7.2), cả hai ternary.c và builtin.c có danh sách tương tự lắp ráp (nơi mà nó vấn đề):

builtin.s:

.file "builtin.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 27(%esp) 
    cmpb $99, 27(%esp) 
    sete %al 
    movzbl %al, %eax 
    testl %eax, %eax 
    je .L2 
    movl $.LC0, 28(%esp) 
    jmp .L3 
.L2: 
    movl $.LC1, 28(%esp) 
.L3: 
    movl 28(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

ternary.s:

.file "ternary.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 31(%esp) 
    cmpb $99, 31(%esp) 
    sete %al 
    movzbl %al, %eax 
    testl %eax, %eax 
    je .L2 
    movl $.LC0, %eax 
    jmp .L3 
.L2: 
    movl $.LC1, %eax 
.L3: 
    movl %eax, 24(%esp) 
    movl 24(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

Trong khi nobuiltin.c không:

.file "nobuiltin.c" 
    .section .rodata 
.LC0: 
    .string "Took expected branch!\n" 
.LC1: 
    .string "Boo!\n" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    call getchar 
    movb %al, 27(%esp) 
    cmpb $99, 27(%esp) 
    jne .L2 
    movl $.LC0, 28(%esp) 
    jmp .L3 
.L2: 
    movl $.LC1, 28(%esp) 
.L3: 
    movl 28(%esp), %eax 
    movl %eax, (%esp) 
    call printf 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Debian 4.7.2-4) 4.7.2" 
    .section .note.GNU-stack,"",@progbits 

phần liên quan:

diff

Về cơ bản, __builtin_expect nguyên nhân thêm mã (sete %al ...) được thực hiện trước khi je .L2 dựa vào kết quả của testl %eax, %eax mà CPU có nhiều khả năng để dự đoán như là 1 (giả định ngây thơ, ở đây) thay vì dựa trên so sánh trực tiếp của char đầu vào với 'c'. Trong khi trong trường hợp nobuiltin.c, không có mã như vậy tồn tại và các je/jne trực tiếp sau so sánh với 'c' (cmp $99).Hãy nhớ rằng, dự đoán nhánh chủ yếu được thực hiện trong CPU, và ở đây GCC chỉ đơn giản là "đặt một cái bẫy" cho bộ dự đoán nhánh CPU để giả định đường dẫn nào sẽ được lấy (thông qua mã phụ và chuyển đổi jejne, mặc dù tôi không có nguồn cho điều này, vì số official optimization manual của Intel không đề cập đến việc xử lý các cuộc gặp gỡ đầu tiên với jejne khác nhau đối với dự đoán chi nhánh! Tôi chỉ có thể giả định nhóm GCC đến đây qua thử và sai).

Tôi chắc chắn có những trường hợp thử nghiệm tốt hơn khi dự đoán nhánh của GCC có thể được xem trực tiếp hơn (thay vì quan sát gợi ý cho CPU), mặc dù tôi không biết cách mô phỏng trường hợp này ngắn gọn/súc tích. (Đoán: nó sẽ có khả năng liên quan đến vòng lặp unrolling trong biên soạn.)

+0

Phân tích rất tốt đẹp, và trình bày kết quả rất đẹp. Cảm ơn vì nỗ lực của bạn. –

+1

Điều này không thực sự hiển thị bất kỳ điều gì khác ngoài '__builtin_expect' không có hiệu lực đối với mã được tối ưu hóa cho x86 (vì bạn đã nói chúng giống nhau với -O3). Lý do duy nhất mà chúng khác nhau trước đây là '__builtin_expect' là một hàm trả về giá trị cho nó, và giá trị trả về đó không thể xảy ra thông qua cờ. Nếu không, sự khác biệt sẽ ở lại trong mã được tối ưu hóa. – ughoavgfhw

+0

@ughoavgfhw: Ý của bạn là gì bởi "giá trị trả về đó không thể xảy ra qua cờ"? –

Các vấn đề liên quan