Untuk arsitektur Intel, apakah ada cara untuk menginstruksikan compiler GCC untuk menghasilkan kode yang selalu memaksa prediksi cabang dengan cara tertentu dalam kode saya? Apakah perangkat keras Intel mendukung ini? Bagaimana dengan kompiler atau perangkat keras lain?
Saya akan menggunakan ini dalam kode C ++ di mana saya tahu kasus saya ingin berjalan cepat dan tidak peduli tentang perlambatan ketika cabang lain perlu diambil bahkan ketika baru-baru ini mengambil cabang itu.
for (;;) {
if (normal) { // How to tell compiler to always branch predict true value?
doSomethingNormal();
} else {
exceptionalCase();
}
}
Sebagai pertanyaan lanjutan untuk Evdzhan Mustafa, dapatkah petunjuk tersebut menentukan petunjuk untuk pertama kalinya prosesor menemukan instruksi, semua prediksi cabang berikutnya, berfungsi normal?
Jawaban:
Mulai C ++ 20, atribut kemungkinan dan tidak mungkin harus distandarisasi dan sudah didukung di g ++ 9 . Jadi seperti yang dibahas di sini , Anda bisa menulis
misalnya dalam kode berikut blok else menjadi inline berkat
[[unlikely]]
blok iftautan godbolt membandingkan ada / tidaknya atribut
sumber
[[unlikely]]
dalamif
vs[[likely]]
dielse
?GCC mendukung fungsi
__builtin_expect(long exp, long c)
untuk menyediakan fitur semacam ini. Anda dapat memeriksa dokumentasinya di sini .Dimana
exp
kondisi yang digunakan danc
merupakan nilai yang diharapkan. Misalnya jika Anda inginKarena sintaksnya yang canggung, ini biasanya digunakan dengan menentukan dua makro kustom seperti
hanya untuk meringankan tugas.
Perhatikan bahwa:
sumber
constexpr
fungsi?constexpr
fungsi tidak dapat menggantikan makro ini. Itu harus dalamif
pernyataan langsung saya percaya. Alasan yang samaassert
tidak pernah bisa menjadi suatuconstexpr
fungsi.constexpr
hanya berbicara tentang semantik nilai, bukan penyejajaran perakitan khusus implementasi); interpretasi langsung (tanpa sebaris) kode tidak ada artinya. Tidak ada alasan sama sekali untuk menggunakan fungsi untuk ini.__builtin_expect
itu sendiri adalah petunjuk pengoptimalan, jadi berpendapat bahwa metode yang menyederhanakan penggunaannya bergantung pada pengoptimalan adalah ... tidak meyakinkan. Selain itu, saya tidak menambahkanconstexpr
penentu untuk membuatnya berfungsi di tempat pertama, tetapi membuatnya bekerja dalam ekspresi konstan. Dan ya, ada alasan untuk menggunakan suatu fungsi. Misalnya, saya tidak ingin mencemari seluruh namespace saya dengan nama kecil yang lucu sepertilikely
. Saya harus menggunakan misalnyaLIKELY
, untuk menekankan bahwa ini adalah makro dan menghindari tabrakan, tapi itu jelek.gcc memiliki __builtin_expect panjang (exp panjang, c panjang) ( penekanan milik saya ):
Sebagai catatan dokumentasi, Anda sebaiknya memilih untuk menggunakan umpan balik profil aktual dan artikel ini menunjukkan contoh praktis tentang hal ini dan bagaimana dalam kasus mereka setidaknya berakhir dengan peningkatan dibandingkan penggunaan
__builtin_expect
. Lihat juga Bagaimana cara menggunakan pengoptimalan terpandu profil di g ++? .Kami juga dapat menemukan artikel pemula kernel Linux di makro kernal kemungkinan () dan tidak mungkin () yang menggunakan fitur ini:
Perhatikan penggunaan
!!
makro kita dapat menemukan penjelasan untuk ini di Why use !! (condition) bukan (condition)? .Hanya karena teknik ini digunakan di kernel Linux tidak berarti selalu masuk akal untuk menggunakannya. Kita dapat melihat dari pertanyaan ini saya baru-baru ini menjawab perbedaan antara kinerja fungsi ketika melewatkan parameter sebagai konstanta waktu kompilasi atau variabel bahwa banyak teknik pengoptimalan linting tangan tidak berfungsi dalam kasus umum. Kita perlu membuat profil kode dengan hati-hati untuk memahami apakah suatu teknik efektif. Banyak teknik lama bahkan mungkin tidak relevan dengan pengoptimalan compiler modern.
Catatan, meskipun builtin bukan dentang portabel juga mendukung __builtin_expect .
Juga pada beberapa arsitektur mungkin tidak ada bedanya .
sumber
Tidak, tidak ada. (Setidaknya pada prosesor x86 modern.)
__builtin_expect
yang disebutkan di jawaban lain memengaruhi cara gcc mengatur kode assembly. Itu tidak secara langsung mempengaruhi prediktor cabang CPU. Tentu saja, akan ada efek tidak langsung pada prediksi cabang yang disebabkan oleh penyusunan ulang kode. Tetapi pada prosesor x86 modern tidak ada instruksi yang memberitahu CPU "asumsikan cabang ini / tidak diambil".Lihat pertanyaan ini untuk detail selengkapnya: Prediksi Cabang Awalan Intel x86 0x2E / 0x3E benar-benar digunakan?
Agar jelas,
__builtin_expect
dan / atau penggunaan-fprofile-arcs
dapat meningkatkan kinerja kode Anda, baik dengan memberikan petunjuk ke prediktor cabang melalui tata letak kode (lihat Optimalisasi kinerja perakitan x86-64 - Penjajaran dan prediksi cabang ), dan juga meningkatkan perilaku cache dengan menjauhkan kode yang "tidak mungkin" dari kode yang "mungkin".sumber
__builtin_expect
.__builtin_expect
. Jadi ini seharusnya hanya sebuah komentar. Tapi itu tidak salah, jadi saya telah menghapus downvote saya.__builtin_expect
untuk membuat kasus uji yang dapat Anda ukur dengan mudahperf stat
yang akan memiliki tingkat kesalahan prediksi cabang yang sangat tinggi. Ini hanya mempengaruhi tata letak cabang . Dan BTW, Intel sejak Sandybridge atau setidaknya Haswell tidak banyak menggunakan prediksi statis; Selalu ada prediksi di BHT, apakah itu alias basi atau tidak. xania.org/201602/bpu-part-twoCara yang benar untuk menentukan makro yang mungkin / tidak mungkin di C ++ 11 adalah sebagai berikut:
Metode ini kompatibel dengan semua versi C ++, tidak seperti
[[likely]]
, tetapi bergantung pada ekstensi non-standar__builtin_expect
.Saat makro ini ditentukan seperti ini:
Itu dapat mengubah arti
if
pernyataan dan merusak kode. Perhatikan kode berikut:Dan hasilnya:
Seperti yang Anda lihat, definisi dari kemungkinan besar digunakan
!!
sebagai pemeran untukbool
mematahkan semantikif
.Intinya di sini bukanlah itu
operator int()
danoperator bool()
harus terkait. Itu adalah praktik yang baik.Alih-alih menggunakan
!!(x)
alih-alihstatic_cast<bool>(x)
kehilangan konteks untuk konversi kontekstual C ++ 11 .sumber
switch
, terima kasih. Konversi kontekstual yang terlibat di sini adalah partucluar untuk mengetikbool
dan lima konteks khusus yang terdaftar di sana , yang tidak memasukkanswitch
konteks.(_Bool)(condition)
, karena C tidak memiliki operator yang kelebihan beban.(condition)
, tidak!!(condition)
. Keduanyatrue
setelah mengubahnya (diuji dengan g ++ 7.1). Dapatkah Anda membuat contoh yang benar-benar mendemonstrasikan masalah yang Anda bicarakan saat menggunakan!!
booleanisasi?Karena jawaban lain telah cukup disarankan, Anda dapat menggunakan
__builtin_expect
untuk memberikan petunjuk kepada kompiler tentang bagaimana mengatur kode assembly. Seperti yang ditunjukkan oleh dokumen resmi , dalam banyak kasus, assembler yang terpasang di otak Anda tidak akan sebaik yang dibuat oleh tim GCC. Sebaiknya gunakan data profil aktual untuk mengoptimalkan kode Anda, daripada menebak-nebak.Di sepanjang baris yang mirip, tetapi belum disebutkan, adalah cara khusus GCC untuk memaksa compiler membuat kode di jalur "cold". Ini melibatkan penggunaan atribut
noinline
dancold
, yang melakukan persis seperti yang mereka lakukan. Atribut ini hanya dapat diterapkan ke fungsi, tetapi dengan C ++ 11, Anda dapat mendeklarasikan fungsi lambda sebaris dan kedua atribut ini juga dapat diterapkan ke fungsi lambda.Meskipun ini masih termasuk dalam kategori umum pengoptimalan mikro, dan dengan demikian saran standar berlaku — uji jangan menebak — saya rasa ini lebih berguna secara umum daripada
__builtin_expect
. Hampir tidak ada generasi prosesor x86 yang menggunakan petunjuk prediksi cabang ( referensi ), jadi satu-satunya hal yang dapat Anda pengaruhi adalah urutan kode assembly. Karena Anda tahu apa itu penanganan kesalahan atau kode "kasus tepi", Anda dapat menggunakan anotasi ini untuk memastikan bahwa kompilator tidak akan pernah memprediksi cabangnya dan akan menautkannya dari kode "panas" saat mengoptimalkan ukuran.Penggunaan sampel:
Lebih baik lagi, GCC akan secara otomatis mengabaikan ini untuk mendukung umpan balik profil jika tersedia (misalnya, saat menyusun dengan
-fprofile-use
).Lihat dokumentasi resminya di sini: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
sumber
__builtin_expect
dilakukannya. Itu sama sekali tidak berguna. Anda benar bahwacold
atribut tersebut juga berguna, tetapi Anda meremehkan kegunaan__builtin_expect
menurut saya.__builtin_expect dapat digunakan untuk memberi tahu kompiler jalan mana yang Anda harapkan dari sebuah cabang. Ini dapat memengaruhi cara kode dibuat. Prosesor biasa menjalankan kode lebih cepat secara berurutan. Jadi jika Anda menulis
kompiler akan menghasilkan kode seperti
Jika petunjuk Anda benar, ini akan mengeksekusi kode tanpa ada cabang yang benar-benar dilakukan. Ini akan berjalan lebih cepat dari urutan normal, di mana setiap pernyataan if akan bercabang di sekitar kode kondisional dan akan mengeksekusi tiga cabang.
Prosesor x86 yang lebih baru memiliki instruksi untuk cabang yang diharapkan untuk diambil, atau untuk cabang yang diharapkan tidak diambil (ada awalan instruksi; tidak yakin tentang detailnya). Tidak yakin apakah prosesor menggunakan itu. Ini tidak terlalu berguna, karena prediksi cabang akan menangani ini dengan baik. Jadi menurut saya Anda tidak benar-benar dapat memengaruhi prediksi cabang .
sumber
Sehubungan dengan OP, tidak, tidak ada cara di GCC untuk memberi tahu prosesor untuk selalu menganggap cabang telah diambil atau tidak. Apa yang Anda miliki adalah __builtin_expect, yang melakukan apa yang dikatakan orang lain. Selain itu, saya pikir Anda tidak ingin memberitahu prosesor apakah cabang diambil atau tidak selalu . Prosesor masa kini, seperti arsitektur Intel, dapat mengenali pola yang cukup kompleks dan beradaptasi secara efektif.
Namun, ada kalanya Anda ingin mengambil kendali apakah secara default sebuah cabang diprediksi diambil atau tidak: Ketika Anda tahu kode tersebut akan disebut "cold" sehubungan dengan statistik percabangan.
Satu contoh konkret: Kode manajemen pengecualian. Menurut definisi, kode manajemen akan terjadi secara luar biasa, tetapi mungkin ketika itu terjadi, kinerja maksimum diinginkan (mungkin ada kesalahan kritis yang harus ditangani secepat mungkin), oleh karena itu Anda mungkin ingin mengontrol prediksi default.
Contoh lain: Anda dapat mengklasifikasikan input Anda dan beralih ke kode yang menangani hasil klasifikasi Anda. Jika ada banyak klasifikasi, prosesor dapat mengumpulkan statistik tetapi kehilangannya karena klasifikasi yang sama tidak segera terjadi dan sumber daya prediksi dikhususkan untuk kode yang baru-baru ini dipanggil. Saya berharap akan ada cara primitif untuk memberi tahu prosesor "tolong jangan mencurahkan sumber daya prediksi untuk kode ini" seperti yang terkadang Anda katakan "jangan simpan ini ke cache".
sumber