Kode mana yang lebih baik untuk optimasi prediksi cabang?

10

Dengan prediksi cabang, dan juga pengaruh optimisasi kompiler, kode mana yang cenderung menawarkan kinerja superior?

Perhatikan bahwa bRareExceptionPresent mewakili kondisi yang tidak biasa. Ini bukan jalur logika yang normal.

/* MOST COMMON path must branch around IF clause */

bool SomeFunction(bool bRareExceptionPresent)
{
  // abort before function
  if(bRareExceptionPresent)
  {
     return false;
  }    
  .. function primary body ..    
  return true;
}

/* MOST COMMON path does NOT branch */

bool SomeFunction(bool bRareExceptionPresent)
{
  if(!bRareExceptionPresent)
  {
    .. function primary body ..
  }
  else
  {
    return false;
  }
  return true;
}
dyasta
sumber
9
Saya akan pergi mengambil risiko di sini dan mengatakan tidak ada perbedaan apa pun.
Robert Harvey
7
Ini mungkin tergantung pada CPU spesifik yang Anda kompilasi, karena mereka memiliki arsitektur perpipaan yang berbeda (slot tunda vs tanpa slot tunda). Waktu yang Anda habiskan untuk memikirkan hal ini kemungkinan jauh lebih banyak daripada waktu yang dihemat saat menjalankan profil terlebih dahulu, kemudian dioptimalkan.
2
Ini hampir pasti prematur mikro-optimasi.
Robert Harvey
2
@MichaelT Ya, pembuatan profil memang satu-satunya cara yang dapat diandalkan untuk mengetahui apa yang sebenarnya terjadi dengan kinerja untuk kode pada target, platform, dalam konteksnya. Namun, saya ingin tahu apakah umumnya disukai.
dyasta
1
@RobertHarvey: Ini adalah optimasi mikro prematur, kecuali dalam kasus di mana kedua kondisi terpenuhi: (1) loop disebut miliaran (bukan jutaan) kali; dan (2) ironisnya, ketika badan loop kecil dalam hal kode mesin. Kondisi # 2 berarti bahwa sebagian kecil dari waktu yang dihabiskan untuk overhead tidak signifikan dibandingkan dengan waktu yang dihabiskan untuk pekerjaan yang bermanfaat. Kabar baiknya adalah bahwa biasanya, dalam situasi seperti itu di mana kedua kondisi terpenuhi, SIMD (vektorisasi), yang pada dasarnya tanpa cabang, akan menyelesaikan semua masalah kinerja.
rwong

Jawaban:

10

Di dunia saat ini, tidak masalah, jika tidak ada sama sekali.

Prediksi cabang dinamis (sesuatu yang dipikirkan selama beberapa dekade (lihat Analisis Skema Prediksi Cabang Dinamis Beban Kerja Sistem yang diterbitkan pada tahun 1996)) adalah tempat yang cukup umum.

Contohnya dapat ditemukan pada prosesor ARM. Dari Pusat Info Arm pada Prediksi Cabang

Untuk meningkatkan akurasi prediksi cabang, kombinasi teknik statis dan dinamis digunakan.

Pertanyaannya kemudian adalah "apa prediksi cabang dinamis di prosesor lengan?" Pembacaan berkelanjutan prediksi cabang dinamis menunjukkan bahwa ia menggunakan skema prediksi 2 bit (dijelaskan dalam makalah) membangun informasi tentang apakah cabang diambil dengan kuat atau lemah atau tidak diambil.

Seiring waktu (dan maksud saya beberapa melewati blok itu) ini membangun informasi ke mana kode akan pergi.

Untuk prediksi statis , ini terlihat pada cara kode terlihat dengan sendirinya dan ke arah mana cabang dibuat pada tes - untuk instruksi sebelumnya atau yang lebih jauh dalam kode:

Skema yang digunakan dalam prosesor ARM1136JF-S memprediksi bahwa semua cabang bersyarat ke depan tidak diambil dan semua cabang terbelakang diambil. Sekitar 65% dari semua cabang didahului oleh siklus non-cabang yang cukup untuk diprediksi sepenuhnya.

Seperti disebutkan oleh Sparky, ini didasarkan pada pemahaman bahwa loop lebih sering daripada tidak, loop. Cabang-cabang loop mundur (memiliki cabang di akhir loop untuk memulai kembali di atas) - biasanya melakukan ini.

Bahaya mencoba menebak kompiler adalah Anda tidak tahu bagaimana kode itu akan dikompilasi (dan dioptimalkan). Dan sebagian besar, itu tidak masalah. Dengan prediksi dinamis, dua kali melalui fungsi ini akan memprediksi melewatkan pernyataan penjaga untuk pengembalian prematur. Jika kinerja dua pipeline memerah adalah kinerja kritis, ada hal lain yang perlu dikhawatirkan.

Waktu yang diperlukan untuk membaca satu gaya di atas yang lain kemungkinan lebih penting - membuat kode menjadi bersih sehingga manusia dapat membacanya, karena kompiler akan melakukannya dengan baik tidak peduli seberapa berantakan atau ideal Anda menulis kode.


sumber
7
Sebuah pertanyaan stackoverflow yang terkenal menunjukkan bahwa prediksi cabang itu penting, bahkan hari ini.
Florian Margaine
3
@FlorianMargaine sementara itu penting, itu mendapatkan dalam situasi di mana itu benar-benar penting tampaknya memerlukan pemahaman tentang apa yang Anda kompilasi dan bagaimana cara kerjanya (lengan vs x86 vs mips ...). Menulis kode yang mencoba melakukan optimasi mikro ini pada awalnya kemungkinan bekerja dari tempat yang salah dan tidak mencapai efek yang diinginkan.
Yah tentu saja, jangan mengutip DK. Tapi saya pikir pertanyaan ini jelas dalam arti optimasi, ketika Anda sudah melewati tahap pembuatan profil. :-)
Florian Margaine
2
@MichaelT Jawaban yang bagus, dan saya sangat setuju dengan kesimpulan Anda. Optimalisasi pra-profil / abstrak semacam ini dapat menjadi kontra-produktif. Ini akhirnya menjadi permainan tebak-tebakan, menyebabkan orang membuat keputusan desain karena alasan yang tidak masuk akal. Namun, saya masih penasaran; o
dyasta
5
@ 90h stackoverflow.com/questions/11227809/…
Florian Margaine
9

Pemahaman saya adalah bahwa pertama kali CPU menemukan cabang, itu akan memprediksi (jika didukung) bahwa cabang maju tidak diambil dan cabang mundur adalah. Alasan untuk ini adalah bahwa loop (yang biasanya bercabang mundur) diasumsikan diambil.

Pada beberapa prosesor, Anda dapat memberikan petunjuk dalam instruksi perakitan tentang jalur mana yang lebih mungkin. Rincian ini luput dari saya saat ini.

Selain itu, beberapa kompiler C juga mendukung prediksi cabang statis sehingga Anda dapat memberi tahu kompiler cabang mana yang lebih mungkin. Pada gilirannya itu dapat mengatur ulang kode yang dihasilkan, atau menggunakan instruksi yang dimodifikasi untuk memanfaatkan informasi ini (atau bahkan mengabaikannya saja)

__builtin_expect((long)!!(x), 1L)  /* GNU C to indicate that <x> will likely be TRUE */
__builtin_expect((long)!!(x), 0L)  /* GNU C to indicate that <x> will likely be FALSE */

Semoga ini membantu.

Sparky
sumber
3
"Pemahaman saya adalah bahwa pertama kali CPU menemukan cabang, itu akan memprediksi (jika didukung) bahwa cabang maju tidak diambil dan cabang mundur adalah." Ini adalah pemikiran yang sangat menarik. Apakah Anda punya bukti bahwa ini memang diterapkan dalam arsitektur umum?
blubb
5
Langsung dari mulut kuda: Cabang maju default untuk tidak diambil. Cabang terbelakang default untuk diambil . Dan dari halaman yang sama: "awalan 0x3E - secara statis memprediksi cabang yang diambil".
MSalters
Apakah ada pragma platform agnostik yang setaraf __builtin_expect?
MarcusJ