Di setiap bahasa pemrograman ada set opcode yang direkomendasikan daripada yang lain. Saya sudah mencoba daftar mereka di sini, dalam urutan kecepatan.
- Bitwise
- Penambahan / Pengurangan Integer
- Penggandaan / Divisi Integer
- Perbandingan
- Mengontrol aliran
- Penambahan / Pengurangan Float
- Penggandaan / Divisi Float
Di mana Anda memerlukan kode berkinerja tinggi, C ++ dapat dioptimalkan secara tangan dalam perakitan, untuk menggunakan instruksi SIMD atau aliran kontrol yang lebih efisien, tipe data, dll. Jadi saya mencoba memahami apakah tipe data (int32 / float32 / float64) atau operasi yang digunakan ( *
, +
, &
) mempengaruhi kinerja di tingkat CPU.
- Apakah satu kali lipat lebih lambat pada CPU daripada penambahan?
- Dalam teori MCU Anda belajar bahwa kecepatan opcodes ditentukan oleh jumlah siklus CPU yang diperlukan untuk mengeksekusi. Jadi, apakah ini berarti bahwa multiply membutuhkan 4 siklus dan add membutuhkan 2?
- Apa saja karakteristik kecepatan dari matematika dasar dan opcode aliran kontrol?
- Jika dua opcode mengambil jumlah siklus yang sama untuk dieksekusi, maka keduanya dapat digunakan secara bergantian tanpa ada untung / rugi kinerja?
- Detail teknis lainnya yang dapat Anda bagikan mengenai kinerja CPU x86 dihargai
c++
performance
optimization
Robinicks
sumber
sumber
Jawaban:
Panduan optimasi Agner Fog sangat baik. Ia memiliki panduan, tabel timing instruksi, dan dokumen tentang arsitektur mikro semua desain CPU x86 terbaru (kembali sejauh Intel Pentium). Lihat juga beberapa sumber lain yang ditautkan dari /programming//tags/x86/info
Hanya untuk bersenang-senang, saya akan menjawab beberapa pertanyaan (angka dari CPU Intel terbaru). Pilihan ops bukanlah faktor utama dalam mengoptimalkan kode (kecuali Anda dapat menghindari pembagian.)
Ya (kecuali jika kekuatan 2). (3-4x latency, dengan hanya satu throughput clock pada Intel.) Namun, jangan jauh-jauh untuk menghindarinya, karena itu secepat 2 atau 3 menambahkan.
Lihat tabel instruksi Agner Fog dan panduan arsitektur mikro jika Anda ingin tahu persis : P. Hati-hati dengan lompatan bersyarat. Lompatan tanpa syarat (seperti pemanggilan fungsi) memiliki beberapa overhead kecil, tetapi tidak banyak.
Tidak, mereka mungkin bersaing untuk port eksekusi yang sama dengan yang lain, atau mereka mungkin tidak. Itu tergantung pada rantai ketergantungan apa yang bisa dikerjakan CPU secara paralel. (Dalam praktiknya, biasanya tidak ada keputusan yang berguna untuk dibuat. Kadang-kadang muncul bahwa Anda dapat menggunakan pergeseran vektor atau pengocokan vektor, yang berjalan pada port yang berbeda pada CPU Intel. Tetapi pergeseran demi byte dari seluruh register (
PSLLDQ
dll. berjalan di unit acak.)Dokumen microarch Agner Fog menggambarkan jalur pipa Intel dan AMD CPU secara cukup rinci untuk menentukan dengan tepat berapa banyak siklus yang harus diambil per iterasi, dan apakah hambatannya adalah throughput, rantai dependensi, atau pertikaian untuk satu port eksekusi. Lihat beberapa jawaban saya di StackOverflow, seperti ini atau ini .
Juga, http://www.realworldtech.com/haswell-cpu/ (dan serupa untuk desain sebelumnya) adalah bacaan yang menyenangkan jika Anda menyukai desain CPU.
Inilah daftar Anda, yang diurutkan untuk CPU Haswell, berdasarkan daftar tamu terbaik saya. Ini sebenarnya bukan cara yang berguna untuk memikirkan sesuatu untuk apa pun selain menyetel loop asm. Efek prediksi cache / cabang biasanya mendominasi, jadi tuliskan kode Anda untuk memiliki pola yang baik. Angka-angka sangat bergelombang, dan mencoba untuk menghitung latensi tinggi, bahkan jika throughput tidak menjadi masalah, atau untuk menghasilkan lebih banyak uops yang menyumbat pipa untuk hal-hal lain terjadi secara paralel. Esp. nomor cache / cabang sangat dibuat-buat. Latensi penting untuk dependensi yang dibawa loop, throughput penting ketika setiap iterasi independen.
TL: DR angka-angka ini dibuat berdasarkan apa yang saya bayangkan untuk kasus penggunaan "khas", sejauh pertukaran antara latensi, hambatan pelabuhan eksekusi, dan throughput front-end (atau kios untuk hal-hal seperti kehilangan cabang) ). Tolong jangan gunakan angka-angka ini untuk segala jenis analisis perf serius .
shift dan rotate (comp-time const count) /
versi vektor dari semua ini (1 hingga 4 per siklus throughput, 1 siklus latensi)
tmp += 7
dalam satu lingkaran bukantmp = i*7
)sum
variabel. (Saya bisa menimbang ini dan fp mul serendah 1 atau setinggi 5 tergantung pada use-case)._mm_insert_epi8
, dll.)y = x ? a : b
, atauy = x >= 0
) (test / setcc
ataucmov
)%
dengan konstanta waktu kompilasi (non-power of 2).PHADD
menambahkan nilai dalam vektor)Saya benar-benar mengada-ada berdasarkan dugaan . Jika ada sesuatu yang salah, itu karena saya sedang memikirkan use-case yang berbeda, atau kesalahan editing.
Biaya relatif dari hal-hal pada CPU AMD akan serupa, kecuali mereka memiliki shif integer yang lebih cepat ketika shift-count adalah variabel. CPU AMD Bulldozer-family tentu saja lebih lambat pada sebagian besar kode, karena berbagai alasan. (Ryzen cukup pandai dalam banyak hal).
Ingatlah bahwa benar-benar mustahil untuk merebus berbagai hal menjadi biaya satu dimensi . Selain cache-miss dan branch mispredicts, bottleneck dalam suatu blok kode bisa latensi, total throughput uop (frontend), atau throughput port tertentu (port eksekusi).
Operasi "lambat" seperti divisi FP bisa sangat murah jika kode di sekitarnya membuat CPU sibuk dengan pekerjaan lain . (vector FP div atau sqrt masing-masing 1 uop, mereka hanya memiliki latency dan throughput yang buruk. Mereka hanya memblokir unit divide, bukan seluruh port eksekusi yang ada di dalamnya. Integer div adalah beberapa uops.) Jadi jika Anda hanya memiliki satu FP divide untuk setiap ~ 20 mul dan tambahkan, dan ada pekerjaan lain yang harus dilakukan CPU (misal pengulangan loop independen), maka "biaya" dari FP FP bisa kira-kira sama dengan mul FP. Ini mungkin adalah contoh terbaik dari sesuatu yang throughput rendah ketika semua yang Anda lakukan, tetapi dicampur dengan sangat baik dengan kode lain (ketika latensi bukan faktor), karena total rendah uops.
Perhatikan bahwa pembagian integer hampir tidak ramah terhadap kode di sekitarnya: Di Haswell, 9 uops, dengan satu per 8-11c throughput, dan latensi 22-29c. (Pembagian 64bit jauh lebih lambat, bahkan pada Skylake.) Jadi angka latensi dan throughput agak mirip dengan FP div, tetapi FP div hanya satu uop.
Untuk contoh-contoh menganalisa urutan pendek dari lns untuk throughput, latency, dan total uops, lihat beberapa jawaban SO saya:
sum += x[i] * y[i]
dengan membuka gulungan dengan beberapa akumulator vektor untuk menyembunyikan latensi FMA. Ini cukup teknis dan tingkat rendah, tetapi menunjukkan kepada Anda jenis keluaran bahasa rakitan yang ingin Anda buat oleh kompiler Anda, dan mengapa itu penting.IDK jika orang lain menulis jawaban SO termasuk analisis semacam ini. Saya memiliki waktu yang jauh lebih mudah untuk menemukan sendiri, karena saya tahu saya sering membahas detail ini, dan saya dapat mengingat apa yang saya tulis.
sumber
Tergantung pada CPU yang dimaksud, tetapi untuk CPU modern daftarnya adalah seperti ini:
Tergantung pada CPU mungkin ada banyak tol untuk bekerja dengan tipe data 64 bit.
Pertanyaan Anda:
if
apa yang dapat Anda lakukan secara wajar dengan aritmatika.Dan akhirnya, jika Anda membuat game, jangan terlalu khawatir tentang semua ini, lebih baik berkonsentrasi untuk membuat game yang bagus daripada memotong siklus CPU.
sumber
Saya membuat tes tentang operasi penyihir bilangan bulat satu juta kali pada x64_64, mencapai kesimpulan singkat seperti di bawah ini,
tambahkan --- 116 mikrodetik
sub ---- 116 mikrodetik
mul ---- 1036 mikrodetik
div ---- 13037 mikrodetik
data di atas telah mengurangi overhead yang disebabkan oleh loop,
sumber
Manual prosesor intel dapat diunduh gratis dari situs web mereka. Mereka cukup besar tetapi secara teknis dapat menjawab pertanyaan Anda. Manual optimasi secara khusus adalah apa yang Anda cari, tetapi manual instruksi juga memiliki timing dan latency untuk sebagian besar jalur CPU utama untuk instruksi simd karena mereka bervariasi dari chip ke chip.
Secara umum, saya akan mempertimbangkan cabang penuh serta pengejaran penunjuk (lintasan daftar tautan, memanggil fungsi virtual) yang terbaik untuk perf killers, tetapi CPU x86 / x64 sangat bagus di keduanya, dibandingkan dengan arsitektur lain. Jika Anda pernah port ke platform lain, Anda akan melihat seberapa besar masalah mereka, jika Anda menulis kode kinerja tinggi.
sumber