Saya menemukan #define
di mana mereka menggunakan __builtin_expect
.
Dokumentasi mengatakan:
Fungsi bawaan:
long __builtin_expect (long exp, long c)
Anda dapat menggunakan
__builtin_expect
untuk memberikan informasi prediksi cabang kepada kompiler. Secara umum, Anda harus memilih untuk menggunakan umpan balik profil aktual untuk ini (-fprofile-arcs
), karena pemrogram terkenal buruk dalam memprediksi bagaimana kinerja program mereka sebenarnya. Namun, ada aplikasi di mana data ini sulit dikumpulkan.Nilai kembali adalah nilai
exp
, yang harus merupakan ekspresi integral. Semantik dari built-in adalah seperti yang diharapkanexp == c
. Sebagai contoh:if (__builtin_expect (x, 0)) foo ();
akan menunjukkan bahwa kami tidak berharap untuk menelepon
foo
, karena kami berharapx
menjadi nol.
Jadi mengapa tidak langsung menggunakan:
if (x)
foo ();
bukannya sintaks yang rumit dengan __builtin_expect
?
if ( x == 0) {} else foo();
.. atau hanyaif ( x != 0 ) foo();
yang setara dengan kode dari dokumentasi GCC.Jawaban:
Bayangkan kode perakitan yang akan dihasilkan dari:
Saya kira itu harus seperti:
Anda dapat melihat bahwa instruksi diatur sedemikian rupa sehingga
bar
kasing mendahului kasingfoo
(berlawanan dengan kode C). Ini dapat memanfaatkan pipa CPU dengan lebih baik, karena lompatan meronta-ronta instruksi yang sudah diambil.Sebelum lompatan dieksekusi, instruksi di bawahnya (
bar
kasing) didorong ke pipa. Karenafoo
kasus ini tidak mungkin, melompat juga tidak mungkin, maka meronta-ronta pipa tidak mungkin.sumber
x = 0
jadi bar diberikan pertama kali. Dan foo, didefinisikan kemudian karena kemungkinannya (lebih tepatnya menggunakan probabilitas) kurang, kan?Mari kita dekompilasi untuk melihat apa yang dilakukan GCC 4.8 dengan itu
Blagovest menyebutkan inversi cabang untuk meningkatkan pipa, tetapi apakah penyusun saat ini benar-benar melakukannya? Ayo cari tahu!
Tanpa
__builtin_expect
Kompilasi dan dekompilasi dengan GCC 4.8.2 x86_64 Linux:
Keluaran:
Urutan instruksi dalam memori tidak berubah: pertama
puts
dan kemudianretq
kembali.Dengan
__builtin_expect
Sekarang ganti
if (i)
dengan:dan kami mendapatkan:
The
puts
dipindahkan ke akhir fungsi, yangretq
kembali!Kode baru ini pada dasarnya sama dengan:
Optimasi ini tidak dilakukan dengan
-O0
.Tapi semoga berhasil menulis contoh yang berjalan lebih cepat dengan
__builtin_expect
tanpa, CPU benar-benar pintar saat itu . Upaya naif saya ada di sini .C ++ 20
[[likely]]
dan[[unlikely]]
C ++ 20 telah membuat standar C ++ built-in: Bagaimana cara menggunakan atribut C ++ 20 yang kemungkinan / tidak mungkin dalam pernyataan if-else Mereka kemungkinan besar (pun!) Melakukan hal yang sama.
sumber
Idenya
__builtin_expect
adalah untuk memberi tahu kompiler bahwa Anda biasanya akan menemukan bahwa ekspresi mengevaluasi ke c, sehingga kompiler dapat mengoptimalkan untuk kasus itu.Saya kira seseorang mengira mereka pintar dan mempercepat hal ini.
Sayangnya, kecuali situasinya dipahami dengan sangat baik (kemungkinan mereka tidak melakukan hal seperti itu), itu mungkin akan memperburuk keadaan. Dokumentasi bahkan mengatakan:
Secara umum, Anda tidak boleh menggunakan
__builtin_expect
kecuali:sumber
__builtin_expect
atau tidak . Di sisi lain, kompiler dapat melakukan banyak optimasi berdasarkan probabilitas cabang, seperti mengatur kode sehingga jalur panas bersebelahan, memindahkan kode tidak mungkin dioptimalkan lebih jauh atau mengurangi ukurannya, mengambil keputusan tentang cabang mana yang akan di vektorisasi, lebih baik penjadwalan jalur panas, dan sebagainya.Nah, seperti yang dikatakan dalam deskripsi, versi pertama menambahkan elemen prediktif ke konstruksi, mengatakan kepada kompiler bahwa
x == 0
cabang lebih mungkin - yaitu, cabang yang akan lebih sering diambil oleh program Anda.Dengan mengingat hal tersebut, kompiler dapat mengoptimalkan persyaratan sehingga membutuhkan jumlah pekerjaan paling sedikit ketika kondisi yang diharapkan berlaku, dengan mengorbankan mungkin harus melakukan lebih banyak pekerjaan jika terjadi kondisi yang tidak terduga.
Lihatlah bagaimana kondisional diterapkan selama fase kompilasi, dan juga dalam perakitan yang dihasilkan, untuk melihat bagaimana satu cabang mungkin kurang bekerja daripada yang lain.
Namun, saya hanya berharap optimasi ini memiliki efek nyata jika kondisi yang dimaksud adalah bagian dari loop dalam yang disebut banyak , karena perbedaan dalam kode yang dihasilkan relatif kecil. Dan jika Anda mengoptimalkannya dengan cara yang salah, Anda mungkin mengurangi kinerja Anda.
sumber
compiler design - Aho, Ullmann, Sethi
:-)Saya tidak melihat jawaban yang menjawab pertanyaan yang saya pikir Anda tanyakan, diparafrasekan:
Judul pertanyaan Anda membuat saya berpikir untuk melakukannya dengan cara ini:
Jika kompilator berasumsi bahwa 'benar' lebih mungkin, ia dapat mengoptimalkan untuk tidak menelepon
foo()
.Masalahnya di sini adalah hanya Anda tidak, secara umum, tahu apa yang akan dianggap oleh kompiler - jadi kode apa pun yang menggunakan teknik semacam ini perlu diukur dengan hati-hati (dan mungkin dipantau dari waktu ke waktu jika konteksnya berubah).
sumber
else
ditinggalkan dari isi posting.Saya mengujinya di Mac menurut @Blagovest Buyukliev dan @Ciro. Kumpulan terlihat jelas dan saya menambahkan komentar;
Perintah adalah
gcc -c -O3 -std=gnu11 testOpt.c; otool -tVI testOpt.o
Ketika saya menggunakan -O3 , tampilannya sama tidak masalah __builtin_expect (i, 0) ada atau tidak.
Ketika dikompilasi dengan -O2 , tampilannya berbeda dengan dan tanpa __builtin_expect (i, 0)
Pertama tanpa
Sekarang dengan __builtin_expect (i, 0)
Untuk meringkas, __builtin_expect berfungsi dalam kasus terakhir.
sumber