Anda dapat menggunakan <<
untuk mengalikan dan >>
untuk membagi angka dalam python ketika saya mengatur waktu saya menemukan menggunakan cara shift biner melakukannya 10x lebih cepat daripada membagi atau mengalikan cara biasa.
Mengapa menggunakan <<
dan >>
jauh lebih cepat daripada *
dan /
?
Apa proses di balik layar yang terjadi *
dan /
sangat lambat?
operators
bitwise-operators
Crizly
sumber
sumber
Jawaban:
Mari kita lihat dua program C kecil yang melakukan sedikit pergeseran dan pembagian.
Ini kemudian masing-masing dikompilasi
gcc -S
untuk melihat apa yang akan menjadi majelis yang sebenarnya.Dengan versi pergeseran bit, dari panggilan ke
atoi
untuk kembali:Sementara versi bagi:
Hanya dengan melihat ini ada beberapa instruksi lebih banyak dalam versi membagi dibandingkan dengan pergeseran bit.
Kuncinya adalah apa yang mereka lakukan?
Dalam versi bit shift, instruksi kuncinya adalah
shll $2, %eax
shift kiri yang logis - ada pembagiannya, dan yang lainnya hanya memindahkan nilai.Dalam versi membagi, Anda dapat melihat
idivl %r8d
- tetapi tepat di atas itu adalahcltd
(konversi panjang menjadi dua kali lipat) dan beberapa logika tambahan di sekitar tumpahan dan muat ulang. Pekerjaan tambahan ini, mengetahui bahwa kita berurusan dengan matematika daripada bit sering diperlukan untuk menghindari berbagai kesalahan yang dapat terjadi dengan hanya melakukan sedikit matematika.Mari kita lakukan beberapa perkalian cepat:
Daripada melewati semua ini, ada satu baris yang berbeda:
Di sini kompiler dapat mengidentifikasi bahwa matematika dapat dilakukan dengan pergeseran, namun alih-alih perubahan logis itu melakukan perubahan aritmatika. Perbedaan antara ini akan jelas jika kita menjalankan ini -
sarl
mempertahankan tanda. Sehingga-2 * 4 = -8
sementarashll
itu tidak.Mari kita lihat ini dalam skrip perl cepat:
Keluaran:
Um ...
-4 << 2
adalah18446744073709551600
yang tidak persis seperti yang Anda harapkan saat berhadapan dengan perkalian dan pembagian. Benar, tapi itu bukan perkalian bilangan bulat.Dan dengan demikian waspada terhadap optimasi prematur. Biarkan kompiler mengoptimalkan untuk Anda - ia tahu apa yang sebenarnya Anda coba lakukan dan kemungkinan akan melakukan pekerjaan yang lebih baik, dengan lebih sedikit bug.
sumber
<< 2
dengan* 4
dan>> 2
dengan/ 4
menjaga arah shift tetap sama dalam setiap contoh.Jawaban yang ada tidak benar-benar membahas sisi perangkat keras, jadi inilah yang sedikit berbeda. Kebijaksanaan konvensional adalah bahwa multiplikasi dan pembagian jauh lebih lambat daripada pergeseran, tetapi kisah aktual saat ini lebih bernuansa.
Sebagai contoh, memang benar bahwa perkalian adalah operasi yang lebih kompleks untuk diimplementasikan dalam perangkat keras, tetapi tidak selalu selalu lebih lambat . Ternyata,
add
juga jauh lebih kompleks untuk diimplementasikan daripadaxor
(atau secara umum operasi bitwise), tetapiadd
(dansub
) biasanya mendapatkan cukup transistor yang didedikasikan untuk operasi mereka yang akhirnya sama cepatnya dengan operator bitwise. Jadi Anda tidak bisa hanya melihat kompleksitas implementasi perangkat keras sebagai panduan untuk kecepatan.Jadi mari kita lihat secara detail pada operator shifting versus operator "penuh" seperti multiplikasi dan shifting.
Bergeser
Pada hampir semua perangkat keras, perpindahan dengan jumlah konstan (yaitu, jumlah yang dapat ditentukan oleh kompiler pada waktu kompilasi) adalah cepat . Secara khusus, biasanya akan terjadi dengan latensi satu siklus, dan dengan throughput 1 per siklus atau lebih baik. Pada beberapa perangkat keras (misalnya, beberapa chip Intel dan ARM), pergeseran tertentu dengan konstanta bahkan mungkin "bebas" karena dapat dibangun ke dalam instruksi lain (
lea
pada Intel, kemampuan pengalihan khusus dari sumber pertama dalam ARM).Bergeser dengan jumlah variabel lebih merupakan area abu-abu. Pada perangkat keras lama, ini kadang-kadang sangat lambat, dan kecepatan berubah dari generasi ke generasi. Sebagai contoh, pada rilis awal P4 Intel, perpindahan dengan jumlah variabel terkenal lambat - membutuhkan waktu yang sebanding dengan jumlah pergeseran! Pada platform itu, menggunakan multiplikasi untuk menggantikan shift bisa menguntungkan (yaitu, dunia telah terbalik). Pada chip Intel sebelumnya, serta generasi berikutnya, bergeser dengan jumlah variabel tidak begitu menyakitkan.
Pada chip Intel saat ini, beralih dengan jumlah variabel tidak terlalu cepat, tetapi juga tidak buruk. Arsitektur x86 adalah sembelih ketika datang ke perubahan variabel, karena mereka mendefinisikan operasi dengan cara yang tidak biasa: menggeser jumlah 0 tidak mengubah flag kondisi, tetapi semua shift lainnya melakukannya. Ini menghambat penggantian nama yang efisien dari register bendera karena tidak dapat ditentukan sampai shift mengeksekusi apakah instruksi selanjutnya harus membaca kode kondisi yang ditulis oleh shift, atau beberapa instruksi sebelumnya. Lebih jauh, shift hanya menulis ke bagian register bendera, yang dapat menyebabkan kios bendera parsial.
Hasilnya adalah bahwa pada arsitektur Intel baru-baru ini, pergeseran dengan jumlah variabel membutuhkan tiga "operasi mikro" sementara sebagian besar operasi sederhana lainnya (tambahkan, operasi bitwise, bahkan penggandaan) hanya mengambil 1. Pergeseran tersebut dapat dijalankan paling banyak sekali setiap 2 siklus .
Perkalian
Tren perangkat keras desktop dan laptop modern adalah membuat operasi perkalian menjadi cepat. Pada chip Intel dan AMD baru-baru ini, pada kenyataannya, satu perkalian dapat dikeluarkan setiap siklus (kami menyebut throughput timbal balik ini ). Namun latensi dari penggandaan adalah 3 siklus. Jadi itu berarti Anda mendapatkan hasil dari setiap siklus perkalian 3 yang diberikan setelah Anda memulainya, tetapi Anda bisa memulai perkalian baru setiap siklus. Nilai mana (1 siklus atau 3 siklus) yang lebih penting tergantung pada struktur algoritma Anda. Jika multiplikasi adalah bagian dari rantai ketergantungan kritis, latensi itu penting. Jika tidak, throughput timbal balik atau faktor lainnya mungkin lebih penting.
Kunci utama yang dapat diambil adalah bahwa pada chip laptop modern (atau lebih baik), perkalian adalah operasi yang cepat, dan cenderung lebih cepat daripada urutan instruksi 3 atau 4 yang akan dikeluarkan oleh kompiler untuk "mendapatkan pembulatan" yang tepat untuk shift yang dikurangi dengan kekuatan. Untuk perubahan variabel, pada Intel, perkalian juga umumnya lebih disukai karena masalah yang disebutkan di atas.
Pada platform faktor bentuk yang lebih kecil, perkalian mungkin masih lebih lambat, karena membangun pengganda 32-bit penuh dan cepat atau terutama 64-bit membutuhkan banyak transistor dan daya. Jika seseorang dapat mengisi dengan rincian kinerja multiply pada chip ponsel baru-baru ini, itu akan sangat dihargai.
Membagi
Divide adalah operasi yang lebih kompleks, perangkat keras, daripada perkalian dan juga jauh lebih jarang terjadi dalam kode aktual - yang berarti bahwa lebih sedikit sumber daya yang kemungkinan dialokasikan untuk itu. Tren chip modern masih mengarah ke pembagi yang lebih cepat, tetapi bahkan chip modern modern memerlukan 10-40 siklus untuk melakukan pembagian, dan mereka hanya sebagian disalurkan melalui pipa. Secara umum, 64-bit membagi bahkan lebih lambat dari 32-bit membagi. Tidak seperti kebanyakan operasi lain, divisi dapat mengambil sejumlah siklus variabel tergantung pada argumen.
Hindari membagi dan ganti dengan shift (atau biarkan kompiler melakukannya, tetapi Anda mungkin perlu memeriksa perakitan) jika Anda bisa!
sumber
BINARY_LSHIFT dan BINARY_RSHIFT adalah proses yang lebih sederhana secara algoritmik daripada BINARY_MULTIPLY dan BINARY_FLOOR_DIVIDE dan mungkin memerlukan lebih sedikit siklus clock. Itu adalah jika Anda memiliki nomor biner dan perlu bithift oleh N, yang harus Anda lakukan adalah menggeser digit di atas banyak ruang dan menggantinya dengan nol. Penggandaan biner pada umumnya lebih rumit , meskipun teknik seperti pengganda Dadda membuatnya cukup cepat.
Memang, adalah mungkin bagi kompiler pengoptimal untuk mengenali kasus ketika Anda mengalikan / membagi dengan kekuatan dua dan menggantinya dengan pergeseran kiri / kanan yang sesuai. Dengan melihat kode byte python yang dibongkar ternyata tidak melakukan ini:
Namun, pada prosesor saya, saya menemukan multiplikasi dan shift kiri / kanan memiliki timing yang sama, dan pembagian lantai (dengan kekuatan dua) sekitar 25% lebih lambat:
sumber