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.)
Apakah satu kali lipat lebih lambat pada CPU daripada penambahan?
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.
Apa saja karakteristik kecepatan dari matematika dasar dan opcode aliran kontrol?
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.
Jika dua opcode mengambil jumlah siklus yang sama untuk dieksekusi, maka keduanya dapat digunakan secara bergantian tanpa ada untung / rugi kinerja?
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.)
Detail teknis lainnya yang dapat Anda bagikan mengenai kinerja CPU x86 dihargai
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 .
- 0,5 hingga 1 Bitwise / Integer Addition / Subtraction /
shift dan rotate (comp-time const count) /
versi vektor dari semua ini (1 hingga 4 per siklus throughput, 1 siklus latensi)
- 1 vektor min, maks, bandingkan-sama, bandingkan-lebih besar (untuk membuat topeng)
- 1,5 vektor mengocok. Haswell dan yang lebih baru hanya memiliki satu port shuffle, dan bagi saya itu umum perlu banyak pengocokan jika Anda memerlukannya, jadi saya menimbangnya sedikit lebih tinggi untuk mendorong berpikir tentang menggunakan shuffles yang lebih sedikit. Mereka tidak gratis, khususnya. jika Anda memerlukan topeng kontrol pshufb dari memori.
- 1,5 load / store (hit cache L1. Throughput lebih baik daripada latensi)
- 1.75 Penggandaan Integer (3c latency / satu per 1c tput pada Intel, 4c lat pada AMD dan hanya satu per 2c tput). Konstanta kecil bahkan lebih murah menggunakan LEA dan / atau ADD / SUB / shift . Tapi tentu saja konstanta kompilasi waktu selalu baik, dan sering dapat mengoptimalkan ke hal-hal lain. (Dan kalikan dalam satu lingkaran sering kali dapat dikurangi dengan kekuatan oleh kompiler menjadi
tmp += 7
dalam satu lingkaran bukan tmp = i*7
)
- 1.75 beberapa shuffle vektor 256b (latensi tambahan pada insns yang dapat memindahkan data antara 128b lajur vektor AVX). (Atau 3 hingga 7 di Ryzen di mana lintasan persimpangan perlu lebih banyak uops)
- 2 fp add / sub (dan versi vektor yang sama) (1 atau 2 per siklus throughtput, 3 hingga 5 siklus latensi). Bisa lambat jika Anda menghambat latensi, mis. Menjumlahkan array dengan hanya 1
sum
variabel. (Saya bisa menimbang ini dan fp mul serendah 1 atau setinggi 5 tergantung pada use-case).
- 2 vektor fp mul atau FMA. (x * y + z semurah mul atau add jika Anda mengkompilasi dengan dukungan FMA diaktifkan).
- 2 memasukkan / mengekstrak register tujuan umum ke dalam elemen vektor (
_mm_insert_epi8
, dll.)
- 2,25 vektor int mul (elemen 16-bit atau pmaddubsw melakukan 8 * 8 -> 16-bit). Lebih murah di Skylake, dengan throughput yang lebih baik daripada skalar mul
- 2,25 shift / rotate by count variabel (latensi 2c, satu throughput 2c pada Intel, lebih cepat pada AMD atau dengan BMI2)
- 2.5 Perbandingan tanpa bercabang (
y = x ? a : b
, atau y = x >= 0
) ( test / setcc
atau cmov
)
- 3 int-> konversi float
- 3 Aliran Kontrol diprediksi sempurna (cabang diprediksi, panggilan, kembali).
- 4 vektor int mul (elemen 32-bit) (2 uops, latensi 10c pada Haswell)
- 4 divisi integer atau
%
dengan konstanta waktu kompilasi (non-power of 2).
- 7 ops horizontal vektor (misalnya
PHADD
menambahkan nilai dalam vektor)
- 11 (vektor) Divisi FP (10-13c latensi, satu per 7c throughput atau lebih buruk). (Bisa murah jika jarang digunakan, tetapi throughputnya 6 sampai 40x lebih buruk daripada FP mul)
- 13? Control Flow (cabang yang diprediksi buruk, mungkin 75% dapat diprediksi)
- 13 int divisi ( ya benar , ini lebih lambat dari divisi FP, dan tidak bisa membuat vektor). (perhatikan bahwa kompiler dibagi dengan konstanta menggunakan mul / shift / add dengan konstanta ajaib , dan div / mod dengan kekuatan 2 sangat murah.)
- 16 (vektor) FP sqrt
- 25? memuat (hit cache L3). (toko cache-miss lebih murah daripada banyak.)
- 50? Trigasi FP / exp / log. Jika Anda membutuhkan banyak exp / log dan tidak membutuhkan akurasi penuh, Anda dapat bertukar akurasi untuk kecepatan dengan polinomial yang lebih pendek dan / atau tabel. Anda juga dapat membuat SIMD vektor.
- 50-80? cabang selalu -dispredicted, biaya 15-20 siklus
- 200-400? memuat / menyimpan (cache miss)
- 3000 ??? baca halaman dari file (hit cache disk OS) (membuat angka di sini)
- 20000 ??? halaman baca disk (OS-cache miss, SSD cepat) (nomor yang benar-benar dibuat-buat)
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:
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.