Semua instruksi berikut melakukan hal yang sama: disetel %eax
ke nol. Cara mana yang optimal (membutuhkan siklus mesin paling sedikit)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
performance
assembly
optimization
x86
micro-optimization
balajimc55.dll
sumber
sumber
Jawaban:
Ringkasan TL; DR :
xor same, same
adalah pilihan terbaik untuk semua CPU . Tidak ada metode lain yang memiliki keunggulan di atasnya, dan setidaknya memiliki beberapa keunggulan dibandingkan metode lain. Ini secara resmi direkomendasikan oleh Intel dan AMD, dan apa yang dilakukan oleh kompiler. Dalam mode 64-bit, tetap gunakanxor r32, r32
, karena penulisan 32-bit reg nol di atas 32 .xor r64, r64
adalah pemborosan byte, karena memerlukan awalan REX.Lebih buruk lagi, Silvermont hanya mengenali
xor r32,r32
sebagai dep-breaking, bukan 64-bit operand-size. Jadi, meskipun awalan REX masih diperlukan karena Anda memusatkan perhatian pada r8..r15, gunakanxor r10d,r10d
, bukanxor r10,r10
.Contoh GP-integer:
Mengosongkan register vektor biasanya paling baik dilakukan dengan
pxor xmm, xmm
. Biasanya itulah yang dilakukan gcc (bahkan sebelum digunakan dengan instruksi FP).xorps xmm, xmm
bisa masuk akal. Ini satu byte lebih pendek daripxor
, tetapixorps
membutuhkan port eksekusi 5 di Intel Nehalem, sementarapxor
dapat berjalan di port apa pun (0/1/5). (Latensi penundaan bypass 2c Nehalem antara integer dan FP biasanya tidak relevan, karena eksekusi yang tidak sesuai pesanan biasanya dapat menyembunyikannya di awal rantai ketergantungan baru).Pada mikroarsitektur keluarga-SnB, tidak ada rasa xor-zeroing yang bahkan membutuhkan port eksekusi. Pada AMD, dan pra-Nehalem P6 / Core2 Intel,
xorps
danpxor
ditangani dengan cara yang sama (seperti instruksi vektor-integer).Menggunakan versi AVX dari instruksi vektor 128b nol bagian atas dari reg juga, jadi
vpxor xmm, xmm, xmm
adalah pilihan yang baik untuk memusatkan YMM (AVX1 / AVX2) atau ZMM (AVX512), atau ekstensi vektor masa depan.vpxor ymm, ymm, ymm
tidak membutuhkan byte tambahan untuk dikodekan, dan berjalan sama pada Intel, tetapi lebih lambat pada AMD sebelum Zen2 (2 uops). Pengosongan AVX512 ZMM akan membutuhkan byte tambahan (untuk awalan EVEX), jadi pengenolan XMM atau YMM harus lebih disukai.Contoh XMM / YMM / ZMM
Lihat Apakah vxorps-zeroing pada AMD Jaguar / Bulldozer / Zen lebih cepat dengan register xmm daripada ymm? dan
Apa cara paling efisien untuk menghapus satu atau beberapa register ZMM di Knights Landing?
Semi terkait: Cara tercepat untuk menyetel nilai __m256 ke semua SATU bit dan
Setel semua bit dalam register CPU ke 1 secara efisien juga mencakup register
k0..7
mask AVX512 . SSE / AVXvpcmpeqd
merusak banyak (meskipun masih membutuhkan uop untuk menulis 1s), tetapi AVX512vpternlogd
untuk reg ZMM bahkan tidak merusak. Di dalam loop pertimbangkan untuk menyalin dari register lain daripada membuat ulang dengan ALU uop, terutama dengan AVX512.Tapi zeroing itu murah: xor-zeroing sebuah xmm reg di dalam loop biasanya sama baiknya dengan menyalin, kecuali pada beberapa CPU AMD (Bulldozer dan Zen) yang memiliki mov-elimination untuk vector regs tetapi masih membutuhkan ALU uop untuk menulis nol untuk xor -zeroing.
Apa yang istimewa tentang memusatkan perhatian pada idiom seperti xor pada berbagai uarches
Beberapa CPU dikenali
sub same,same
sebagai idiom zeroingxor
, tetapi semua CPU yang mengenali idiom zeroing mengenalixor
. Cukup gunakanxor
sehingga Anda tidak perlu khawatir tentang CPU mana yang mengenali idiom zeroing yang mana.xor
(menjadi idiom zeroing yang diakui, tidak sepertimov reg, 0
) memiliki beberapa keuntungan yang jelas dan halus (daftar ringkasan, maka saya akan memperluasnya):mov reg,0
. (Semua CPU)Ukuran kode mesin yang lebih kecil (2 byte, bukan 5) selalu merupakan keuntungan: Kepadatan kode yang lebih tinggi menyebabkan lebih sedikit instruksi-cache yang terlewat, dan pengambilan instruksi yang lebih baik serta berpotensi mendekode bandwidth.
Manfaat tidak menggunakan unit eksekusi untuk xor pada mikroarsitektur keluarga Intel SnB kecil, tetapi menghemat daya. Ini lebih mungkin menjadi masalah pada SnB atau IvB, yang hanya memiliki 3 port eksekusi ALU. Haswell dan yang lebih baru memiliki 4 port eksekusi yang dapat menangani instruksi ALU integer, termasuk
mov r32, imm32
, jadi dengan pengambilan keputusan yang sempurna oleh penjadwal (yang tidak selalu terjadi dalam praktiknya), HSW masih dapat mempertahankan 4 uops per jam bahkan ketika mereka semua membutuhkan ALU port eksekusi.Lihat jawaban saya pada pertanyaan lain tentang register nol untuk lebih jelasnya.
Entri blog Bruce Dawson yang ditautkan oleh Michael Petch (dalam komentar pada pertanyaan) menunjukkan bahwa
xor
ditangani pada tahap ganti nama register tanpa memerlukan unit eksekusi (nol uops di domain yang tidak digunakan), tetapi melewatkan fakta bahwa itu masih satu uop di domain gabungan. CPU Intel modern dapat mengeluarkan & menghentikan 4 uops domain gabungan per jam. Dari situlah 4 nol per batas jam berasal. Kompleksitas yang meningkat dari perangkat keras yang mengganti nama register hanyalah salah satu alasan untuk membatasi lebar desain menjadi 4. (Bruce telah menulis beberapa posting blog yang sangat bagus, seperti serialnya tentang matematika FP dan masalah x87 / SSE / pembulatan , yang saya lakukan Sangat disarankan).Pada CPU AMD Bulldozer-family ,
mov immediate
berjalan pada port eksekusi integer EX0 / EX1 yang sama denganxor
.mov reg,reg
juga dapat berjalan di AGU0 / 1, tetapi itu hanya untuk penyalinan register, bukan untuk pengaturan dari segera. Jadi AFAIK, AMD satu-satunya keuntungan untukxor
lebihmov
adalah encoding lebih pendek. Mungkin juga menghemat sumber daya register fisik, tetapi saya belum melihat tes apa pun.Idiom zeroing yang diakui menghindari penalti register parsial pada CPU Intel yang mengganti nama register parsial secara terpisah dari register penuh (keluarga P6 & SnB).
xor
akan menandai register karena bagian atasnya dikosongkan , jadixor eax, eax
/inc al
/inc eax
hindari penalti register parsial yang biasa dimiliki CPU pra-IvB. Bahkan tanpaxor
, IvB hanya membutuhkan penggabungan uop ketika 8bits (AH
) yang tinggi diubah dan kemudian seluruh register dibaca, dan Haswell bahkan menghapusnya.Dari panduan mikroarch Agner Fog, hal 98 (bagian Pentium M, direferensikan oleh bagian selanjutnya termasuk SnB):
pg82 panduan yang juga menegaskan bahwa
mov reg, 0
ini tidak diakui sebagai idiom zeroing, setidaknya pada P6 awal desain seperti PIII atau PM. Saya akan sangat terkejut jika mereka menghabiskan transistor untuk mendeteksinya di CPU selanjutnya.xor
set flags , yang berarti Anda harus berhati-hati saat menguji kondisi. Karenasetcc
sayangnya hanya tersedia dengan tujuan 8bit , Anda biasanya perlu berhati-hati untuk menghindari penalti pendaftaran sebagian.Akan lebih baik jika x86-64 menggunakan kembali salah satu opcode yang dihapus (seperti AAM) untuk 16/32/64 bit
setcc r/m
, dengan predikat yang dikodekan di bidang sumber-register 3-bit dari bidang r / m (cara beberapa instruksi operan tunggal lainnya menggunakannya sebagai bit opcode). Tapi mereka tidak melakukannya, dan itu tidak akan membantu untuk x86-32.Idealnya, Anda harus menggunakan
xor
/ set flags /setcc
/ read full register:Ini memiliki kinerja yang optimal pada semua CPU (tidak ada stall, penggabungan uops, atau dependensi palsu).
Hal-hal menjadi lebih rumit ketika Anda tidak ingin xor sebelum instruksi pengaturan bendera . misalnya Anda ingin bercabang pada satu kondisi dan kemudian setcc pada kondisi lain dari flag yang sama. misalnya
cmp/jle
,,sete
dan Anda juga tidak memiliki register cadangan, atau Anda inginxor
sama sekali tidak menyertakan jalur kode yang tidak diambil.Tidak ada idiom zeroing yang dikenali yang tidak memengaruhi flag, jadi pilihan terbaik bergantung pada mikroarsitektur target. Pada Core2, menyisipkan uop penggabungan dapat menyebabkan 2 atau 3 siklus berhenti. Tampaknya lebih murah di SnB, tetapi saya tidak menghabiskan banyak waktu untuk mencoba mengukurnya. Menggunakan
mov reg, 0
/setcc
akan berdampak signifikan pada CPU Intel yang lebih lama, dan masih sedikit lebih buruk pada Intel yang lebih baru.Menggunakan
setcc
/movzx r32, r8
mungkin merupakan alternatif terbaik untuk keluarga Intel P6 & SnB, jika Anda tidak dapat xor-zero sebelum instruksi pengaturan bendera. Itu seharusnya lebih baik daripada mengulang tes setelah xor-zeroing. (Jangan pertimbangkansahf
/lahf
ataupushf
/popf
). IvB dapat menghilangkanmovzx r32, r8
(yaitu menanganinya dengan penggantian nama register tanpa unit eksekusi atau latensi, seperti xor-zeroing). Haswell dan yang lebih baru hanya menghilangkanmov
instruksi biasa , jadimovzx
membutuhkan unit eksekusi dan memiliki latensi bukan nol, membuat test /setcc
/movzx
lebih buruk darixor
/ test /setcc
, tetapi masih setidaknya sebagus test /mov r,0
/setcc
(dan jauh lebih baik pada CPU lama).Menggunakan
setcc
/movzx
tanpa zeroing pertama buruk pada AMD / P4 / Silvermont, karena mereka tidak melacak deps secara terpisah untuk sub-register. Akan ada kesalahan dep pada nilai lama register. Menggunakanmov reg, 0
/setcc
untuk zeroing / pemutusan ketergantungan mungkin merupakan alternatif terbaik ketikaxor
/ test /setcc
bukan merupakan pilihan.Tentu saja, jika Anda tidak membutuhkan
setcc
keluaran yang lebih lebar dari 8 bit, Anda tidak perlu nol apapun. Namun, waspadalah terhadap dependensi palsu pada CPU selain P6 / SnB jika Anda memilih register yang baru-baru ini menjadi bagian dari rantai dependensi yang panjang. (Dan berhati-hatilah dalam menyebabkan regangan parsial atau uop ekstra jika Anda memanggil fungsi yang mungkin menyimpan / memulihkan register yang Anda gunakan.)and
dengan nol langsung tidak bersifat khusus karena tidak bergantung pada nilai lama pada CPU mana pun yang saya ketahui, jadi tidak memutus rantai ketergantungan. Ini tidak memiliki kelebihanxor
dan kekurangan.Ini berguna hanya untuk menulis microbenchmark saat Anda menginginkan dependensi sebagai bagian dari uji latensi, tetapi ingin membuat nilai yang diketahui dengan membidik dan menambahkan.
Lihat http://agner.org/optimize/ untuk detail microarch , termasuk idiom zeroing mana yang dikenali sebagai pemutusan ketergantungan (misalnya
sub same,same
pada beberapa tetapi tidak semua CPU, sementaraxor same,same
dikenali pada semua.)mov
Memutus rantai ketergantungan pada nilai lama dari register (terlepas dari nilai sumbernya, nol atau tidak, karena begitulah caramov
kerjanya).xor
hanya memutus rantai ketergantungan dalam kasus khusus di mana src dan tujuan adalah register yang sama, itulah sebabnya mengapamov
ditinggalkan dari daftar pemecah ketergantungan yang dikenali secara khusus . (Juga, karena itu tidak dikenali sebagai idiom zeroing, dengan manfaat lain yang dibawanya.)Menariknya, desain P6 tertua (PPro hingga Pentium III) tidak mengenali
xor
-zeroing sebagai pemecah ketergantungan, hanya sebagai idiom zeroing untuk tujuan menghindari kios register parsial , jadi dalam beberapa kasus sebaiknya gunakan keduanyamov
dan kemudianxor
-Zeroing dalam urutan itu untuk memecahkan dep dan kemudian nol lagi + mengatur bit tag internal bahwa bit tinggi adalah nol jadi EAX = AX = AL.Lihat Contoh Agner Fog 6.17. di microarch pdf-nya. Katanya ini juga berlaku untuk P2, P3, bahkan PM (dini hari?). Sebuah komentar di posting blog tertaut mengatakan bahwa hanya PPro yang memiliki pengawasan ini, tetapi saya telah menguji Katmai PIII, dan @Fanael mengujinya pada Pentium M, dan kami berdua menemukan bahwa itu tidak merusak ketergantungan untuk latensi
imul
rantai terikat . Sayangnya, ini menegaskan hasil Agner Fog.TL: DR:
Jika itu benar-benar membuat kode Anda lebih bagus atau menyimpan instruksi, maka pastikan, nol dengan
mov
untuk menghindari menyentuh bendera, selama Anda tidak memasukkan masalah kinerja selain ukuran kode. Menghindari clobbering flags adalah satu-satunya alasan yang masuk akal untuk tidak menggunakanxor
, tetapi terkadang Anda dapat xor-zero sebelum hal yang menetapkan flag jika Anda memiliki register cadangan.mov
-Nol di depansetcc
lebih baik untuk latensi daripadamovzx reg32, reg8
setelahnya (kecuali pada Intel ketika Anda dapat memilih register yang berbeda), tetapi ukuran kode lebih buruk.sumber
mov reg, src
juga memutus rantai dep untuk OO CPU (terlepas dari src menjadi imm32[mem]
, atau register lain). Pemutusan ketergantungan ini tidak disebutkan dalam manual pengoptimalan karena ini bukan kasus khusus yang hanya terjadi jika src dan tujuan adalah register yang sama. Itu selalu terjadi untuk instruksi yang tidak bergantung pada tujuan mereka. (kecuali untuk implementasi Intelpopcnt/lzcnt/tzcnt
memiliki dep palsu pada tujuan.)mov
gratis, hanya latensi nol. Bagian "tidak mengambil port eksekusi" biasanya tidak penting. Throughput domain-fusi dapat dengan mudah menjadi penghambat, khususnya. dengan beban atau penyimpanan dalam campuran.xor r64, r64
tidak hanya menyia-nyiakan satu byte. Seperti yang Anda katakanxor r32, r32
adalah pilihan terbaik terutama dengan KNL. Lihat bagian 15.7 "Kasus-kasus khusus kemerdekaan" dalam manual micrarch ini jika Anda ingin membaca lebih lanjut.