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:
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 je
và jne
, 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 je
và jne
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.)
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. –
Đ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
@ughoavgfhw: Ý của bạn là gì bởi "giá trị trả về đó không thể xảy ra qua cờ"? –