Itu adalah petunjuk penyusun untuk GCC. Mereka digunakan dalam persyaratan untuk memberi tahu kompiler jika sebuah cabang kemungkinan akan diambil atau tidak. Ini dapat membantu kompiler menyusun kode sedemikian rupa sehingga optimal untuk hasil yang paling sering.
Mereka digunakan seperti ini:
if (likely(some_condition)) {
// the compiler will try and make the code layout optimal for the case
// where some_condition is true, i.e. where this block is run
most_likely_action();
} else {
// this block is less frequently used
corner_case();
}
Itu harus digunakan dengan sangat hati-hati (yaitu berdasarkan hasil profiling cabang yang sebenarnya). Petunjuk yang salah dapat menurunkan kinerja (jelas).
Beberapa contoh bagaimana kode dapat dioptimalkan dengan mudah ditemukan dengan mencari GCC __builtin_expect
. Optimalisasi gcc postingan blog ini:__builtin_expect misalnya merinci pembongkaran dengannya.
Jenis pengoptimalan yang dapat dilakukan sangat spesifik untuk prosesor. Ide umumnya adalah bahwa seringkali, prosesor akan menjalankan kode lebih cepat jika tidak bercabang/melompat di semua tempat. Semakin linier, dan semakin dapat diprediksi cabangnya, semakin cepat ia berjalan. (Hal ini terutama berlaku untuk prosesor dengan pipeline yang dalam, misalnya.)
Jadi kompiler akan mengeluarkan kode sedemikian rupa sehingga cabang yang paling mungkin tidak akan melibatkan lompatan jika itu yang disukai CPU target, misalnya.
Mari kita dekompilasi untuk melihat apa yang dilakukan GCC 4.8 dengannya
Tanpa harapan
#include "stdio.h"
#include "time.h"
int main() {
/* Use time to prevent it from being optimized away. */
int i = !time(NULL);
if (i)
printf("%d\n", i);
puts("a");
return 0;
}
Kompilasi dan dekompilasi dengan GCC 4.8.2 x86_64 Linux:
gcc -c -O3 -std=gnu11 main.c
objdump -dr main.o
Keluaran:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 75 14 jne 24 <main+0x24>
10: ba 01 00 00 00 mov $0x1,%edx
15: be 00 00 00 00 mov $0x0,%esi
16: R_X86_64_32 .rodata.str1.1
1a: bf 01 00 00 00 mov $0x1,%edi
1f: e8 00 00 00 00 callq 24 <main+0x24>
20: R_X86_64_PC32 __printf_chk-0x4
24: bf 00 00 00 00 mov $0x0,%edi
25: R_X86_64_32 .rodata.str1.1+0x4
29: e8 00 00 00 00 callq 2e <main+0x2e>
2a: R_X86_64_PC32 puts-0x4
2e: 31 c0 xor %eax,%eax
30: 48 83 c4 08 add $0x8,%rsp
34: c3 retq
Urutan instruksi dalam memori tidak berubah:pertama printf
lalu puts
dan retq
kembali.
Dengan harapan
Sekarang ganti if (i)
dengan:
if (__builtin_expect(i, 0))
dan kami mendapatkan:
0000000000000000 <main>:
0: 48 83 ec 08 sub $0x8,%rsp
4: 31 ff xor %edi,%edi
6: e8 00 00 00 00 callq b <main+0xb>
7: R_X86_64_PC32 time-0x4
b: 48 85 c0 test %rax,%rax
e: 74 11 je 21 <main+0x21>
10: bf 00 00 00 00 mov $0x0,%edi
11: R_X86_64_32 .rodata.str1.1+0x4
15: e8 00 00 00 00 callq 1a <main+0x1a>
16: R_X86_64_PC32 puts-0x4
1a: 31 c0 xor %eax,%eax
1c: 48 83 c4 08 add $0x8,%rsp
20: c3 retq
21: ba 01 00 00 00 mov $0x1,%edx
26: be 00 00 00 00 mov $0x0,%esi
27: R_X86_64_32 .rodata.str1.1
2b: bf 01 00 00 00 mov $0x1,%edi
30: e8 00 00 00 00 callq 35 <main+0x35>
31: R_X86_64_PC32 __printf_chk-0x4
35: eb d9 jmp 10 <main+0x10>
printf
(dikompilasi ke __printf_chk
) dipindahkan ke akhir fungsi, setelah puts
dan pengembalian untuk meningkatkan prediksi cabang seperti yang disebutkan oleh jawaban lain.
Jadi pada dasarnya sama dengan:
int i = !time(NULL);
if (i)
goto printf;
puts:
puts("a");
return 0;
printf:
printf("%d\n", i);
goto puts;
Pengoptimalan ini tidak dilakukan dengan -O0
.
Tapi semoga berhasil menulis contoh yang berjalan lebih cepat dengan __builtin_expect
daripada tanpa, CPU benar-benar pintar saat itu. Upaya naif saya ada di sini.
C++20 [[likely]]
dan [[unlikely]]
C++20 telah menstandarisasi C++ built-in tersebut:https://stackoverflow.com/questions/51797959/how-to-use-c20s-likely-unlikely-attribute-in-if-else-statement Mereka kemungkinan akan (a pun!) melakukan hal yang sama.