Manakah dari teknik berikut ini yang merupakan pilihan terbaik untuk membagi bilangan bulat dengan 2 dan mengapa?
Teknik 1:
x = x >> 1;
Teknik 2:
x = x / 2;
Berikut x
ini adalah bilangan bulat.
c++
c
optimization
division
micro-optimization
Abhineet
sumber
sumber
x
lagi, tidak ada yang sesuai dengan cara ini: itu harus berupax >>= 1
ataux /= 2
, tergantung pada apa yang ingin Anda ungkapkan dengan operasi. Bukan karena lebih cepat (kompiler modern mana pun akan mengkompilasi semua varian yang setara dengan perakitan yang identik dan cepat) tetapi karena itu kurang membingungkan.x = x >>> 1
. Juga perhatikan bahwa tergantung pada platform dan kompiler mungkin cukup masuk akal untuk secara manual mengoptimalkan divisi dan perkalian menggunakan shift. - Memikirkan pengontrol mikro, misalnya, tanpa dukungan ALU langsung untuk perkalian.x /= 2
karenax >>= 1
terlihat terlalu mirip monadic bind;)x = x / 2
daripada ditulisx /= 2
. Preferensi subjektif mungkin :)⬜=
kombinasinya, ini harus digunakan kapan pun memungkinkan. Ini akan menghapus kebisingan dan menempatkan penekanan pada fakta bahwax
yang dimodifikasi , sedangkan umum=
Operator agak menunjukkan bahwa dibutuhkan pada independen nilai yang sama sekali baru dari yang lama. - Selalu menghindari operator gabungan (sehingga dapat dibaca sehingga seseorang yang hanya tahu operator matematika) mungkin memiliki titik juga, tapi kemudian Anda akan perlu untuk melepaskan sangat berguna++
,--
,+=
, juga.Jawaban:
Gunakan operasi yang paling menggambarkan apa yang Anda coba lakukan.
Perhatikan bahwa mereka tidak persis setara. Mereka dapat memberikan hasil yang berbeda untuk bilangan bulat negatif. Sebagai contoh:
(ideone)
sumber
%
dan/
operator harus konsisten untuk operan positif dan negatif sehingga(a/b)*b+(a%b)==a
benar terlepas dari tanda-tandaa
danb
. Biasanya, penulis akan membuat pilihan yang mendapatkan kinerja terbaik dari CPU.Apakah yang pertama terlihat seperti membagi? Tidak. Jika Anda ingin membelah, gunakan
x / 2
. Kompiler dapat mengoptimalkannya untuk menggunakan bit-shift jika memungkinkan (ini disebut pengurangan kekuatan), yang membuatnya menjadi optimasi mikro yang tidak berguna jika Anda melakukannya sendiri.sumber
Untuk menimbun: ada begitu banyak alasan untuk memilih menggunakan
x = x / 2;
Berikut adalah beberapa:itu mengekspresikan maksud Anda lebih jelas (dengan asumsi Anda tidak berurusan dengan bit twiddling register bit atau sesuatu)
kompiler akan mengurangi ini ke operasi shift
bahkan jika kompilator tidak menguranginya dan memilih operasi yang lebih lambat daripada shift, kemungkinan bahwa ini pada akhirnya mempengaruhi kinerja program Anda dengan cara yang terukur itu sendiri semakin kecil (dan jika itu memengaruhi itu secara terukur, maka Anda memiliki aktual alasan untuk menggunakan shift)
jika divisi akan menjadi bagian dari ekspresi yang lebih besar, Anda lebih mungkin mendapatkan hak diutamakan jika Anda menggunakan operator divisi:
aritmatika yang ditandatangani dapat mempersulit hal-hal bahkan lebih dari masalah yang diutamakan yang disebutkan di atas
untuk mengulangi - kompiler akan tetap melakukan ini untuk Anda. Bahkan, itu akan mengkonversi pembagian dengan konstan ke serangkaian shift, tambah, dan dikalikan untuk semua jenis angka, bukan hanya kekuatan dua. Lihat pertanyaan ini untuk tautan ke informasi lebih lanjut tentang ini.
Singkatnya, Anda tidak membeli apa pun dengan mengkodekan perubahan saat Anda benar-benar bermaksud melipatgandakan atau membagi, kecuali kemungkinan peningkatan kemungkinan memperkenalkan bug. Sudah seumur hidup sejak kompiler tidak cukup pintar untuk mengoptimalkan hal semacam ini ke shift saat yang tepat.
sumber
a/b/c*d
(di manaa..d
dilambangkan variabel numerik) alih-alih jauh lebih mudah dibaca(a*d)/(b*c)
.a*d
ataub*c
akan menghasilkan overflow, formulir yang kurang dapat dibaca tidak setara dan memiliki keuntungan yang jelas. PS Saya setuju bahwa tanda kurung adalah teman baik Anda.a/b/c*d
dalam kode R - dalam konteks di mana melimpah akan berarti bahwa ada sesuatu yang salah dengan data - dan tidak dalam, katakanlah, kinerja-kritis blok kode C).x=x/2;
ini hanya "lebih jelas" daripadax>>=1
jikax
tidak akan pernah menjadi angka negatif ganjil atau orang tidak peduli dengan kesalahan satu per satu. Kalau tidakx=x/2;
danx>>=1;
memiliki arti yang berbeda. Jika yang dibutuhkan seseorang adalah nilai yang dihitungx>>=1
, saya akan menganggapnya lebih jelas daripadax = (x & ~1)/2
ataux = (x < 0) ? (x-1)/2 : x/2
, atau formulasi lain apa pun yang dapat saya pikirkan untuk menggunakan pembagian dengan dua. Demikian juga jika seseorang membutuhkan nilai yang dihitung olehx/=2
, itu lebih jelas daripada((x + ((unsigned)x>>31)>>1)
.Tergantung pada apa yang Anda maksud dengan yang terbaik .
Jika Anda ingin kolega Anda membenci Anda, atau membuat kode Anda sulit dibaca, saya pasti akan memilih opsi pertama.
Jika Anda ingin membagi angka dengan 2, lanjutkan dengan yang kedua.
Keduanya tidak setara, mereka tidak berperilaku sama jika jumlahnya negatif atau di dalam ekspresi yang lebih besar - bitshift memiliki prioritas lebih rendah daripada
+
atau-
, divisi memiliki prioritas lebih tinggi.Anda harus menulis kode Anda untuk mengungkapkan maksudnya. Jika kinerja menjadi perhatian Anda, jangan khawatir, pengoptimal melakukan pekerjaan dengan baik pada optimasi mikro semacam ini.
sumber
Cukup gunakan divide (
/
), anggap itu lebih jelas. Kompiler akan mengoptimalkannya.sumber
ASSUME(x >= 0); x /= 2;
atasx >>= 1;
, tapi itu masih poin penting untuk diangkat.Saya setuju dengan jawaban lain yang harus Anda sukai
x / 2
karena maksudnya lebih jelas, dan kompiler harus mengoptimalkannya untuk Anda.Namun, alasan lain untuk memilih
x / 2
lebihx >> 1
adalah bahwa perilaku>>
adalah implementasi tergantung jikax
adalah ditandatanganiint
dan negatif.Dari bagian 6.5.7, bullet 5 dari standar ISO C99:
sumber
x>>scalepower
bilangan negatif akan tepat apa yang diperlukan ketika membagi nilai dengan kekuatan dua untuk tujuan seperti rendering layar, ketika menggunakanx/scalefactor
akan salah kecuali jika seseorang menerapkan koreksi ke nilai negatif.x / 2
lebih jelas, danx >> 1
tidak jauh lebih cepat (menurut tolok ukur mikro, sekitar 30% lebih cepat untuk Java JVM). Seperti yang telah dicatat orang lain, untuk bilangan negatif pembulatannya sedikit berbeda, jadi Anda harus mempertimbangkan ini ketika Anda ingin memproses bilangan negatif. Beberapa kompiler dapat secara otomatis dikonversix / 2
kex >> 1
jika mereka tahu nomornya tidak boleh negatif (bahkan saya pikir saya tidak dapat memverifikasi ini).Bahkan
x / 2
mungkin tidak menggunakan instruksi CPU divisi (lambat), karena beberapa pintasan dimungkinkan , tetapi masih lebih lambat dari itux >> 1
.(Ini adalah pertanyaan C / C ++, bahasa pemrograman lain memiliki lebih banyak operator. Untuk Java juga ada pergeseran kanan yang tidak ditandai
x >>> 1
, yang lagi-lagi berbeda. Memungkinkan untuk menghitung dengan benar nilai rata-rata (rata-rata) dari dua nilai, sehingga(a + b) >>> 1
akan mengembalikan nilai rata-rata bahkan untuk nilai yang sangat besar daria
danb
. Ini diperlukan misalnya untuk pencarian biner jika indeks array bisa menjadi sangat besar. Ada bug di banyak versi pencarian biner , karena mereka digunakan(a + b) / 2
untuk menghitung rata-rata. tidak berfungsi dengan benar. Solusi yang tepat adalah dengan menggunakan(a + b) >>> 1
.)sumber
x/2
kex>>1
dalam kasus di manax
mungkin negatif. Jika yang diinginkan adalah nilai yangx>>1
akan dihitung, itu hampir pasti akan lebih cepat daripada ungkapan apa pun yang melibatkanx/2
nilai yang sama.x/2
kex>>1
jika dia tahu nilainya tidak negatif. Saya akan mencoba memperbarui jawaban saya.div
instruksi, dengan mengkonversix/2
ke(x + (x<0?1:0)) >> 1
(di mana >> adalah pergeseran kanan aritmatika, yang bergeser dalam bit tanda). Ini membutuhkan 4 instruksi: salin nilainya, shr (untuk mendapatkan bit tanda saja di reg), tambahkan, sar. goo.gl/4F8Ms4Knuth berkata:
Jadi saya sarankan untuk digunakan
x /= 2;
Dengan cara ini kodenya mudah dimengerti dan saya juga berpikir bahwa optimalisasi operasi ini dalam bentuk itu, tidak berarti perbedaan besar untuk prosesor.
sumber
(n+8)>>4
berfungsi dengan baik. Bisakah Anda menawarkan pendekatan apa pun yang sejelas atau seefisien tanpa menggunakan shift kanan yang sudah ditandatangani?Lihatlah output kompiler untuk membantu Anda memutuskan. Saya menjalankan tes ini pada x86-64 dengan
gcc (GCC) 4.2.1 20070719 [FreeBSD]
Juga lihat output kompiler online di godbolt .
Apa yang Anda lihat adalah kompiler tidak menggunakan
sarl
instruksi (aritmatik pergeseran kanan) dalam kedua kasus, sehingga ia mengenali kesamaan antara dua ekspresi. Jika Anda menggunakan pembagian, kompiler juga perlu menyesuaikan untuk angka negatif. Untuk melakukan itu menggeser bit tanda ke bit urutan terendah, dan menambahkannya ke hasilnya. Ini memperbaiki masalah off-by-one ketika menggeser angka negatif, dibandingkan dengan apa yang akan dilakukan pembagian.Karena case divide melakukan 2 shift, sementara case shift eksplisit hanya melakukan satu shift, kita sekarang dapat menjelaskan beberapa perbedaan kinerja yang diukur dengan jawaban lain di sini.
Kode C dengan output perakitan:
Untuk membagi, input Anda adalah
dan ini mengkompilasi ke
sama untuk shift
dengan output:
sumber
d
. Partisi semacam itu berguna untuk banyak tujuan. Bahkan jika seseorang lebih suka memiliki breakpoint di suatu tempat selain antara 0 dan -1, menambahkan offset akan memindahkannya. Divisi bilangan bulat yang memenuhi aksioma kedua akan mempartisi garis bilangan menjadi daerah yang sebagian besar ukurannyad
, tetapi salah satunya adalah ukuran2*d-1
. Tidak persis divisi "sama". Bisakah Anda menawarkan saran kapan partisi eksentball sebenarnya berguna?sar
). goo.gl/KRgIkb . Posting milis ini ( gcc.gnu.org/ml/gcc/2000-04/msg00152.html ) mengkonfirmasi bahwa gcc secara historis menggunakan pergeseran aritmatika untuk int yang ditandatangani, jadi sangat tidak mungkin bahwa FreeBSD gcc 4.2.1 menggunakan pergeseran yang tidak ditandatangani. Saya memperbarui posting Anda untuk memperbaikinya dan paragraf awal mengatakan keduanya digunakan shr, padahal sebenarnya SAR yang mereka gunakan. SHR adalah cara mengekstrak bit tanda untuk/
kasing. Juga termasuk tautan godbolt.Hanya catatan tambahan -
x * = 0,5 akan sering lebih cepat dalam beberapa bahasa berbasis VM - terutama actioncript, karena variabel tidak harus diperiksa untuk membagi dengan 0.
sumber
eval
) membuat itu terjadi lagi setiap kali. Apapun, ya, ini adalah tes yang sangat buruk, karena ini adalah optimasi yang sangat konyol.Gunakan
x = x / 2;
ATAUx /= 2;
Karena ada kemungkinan bahwa programmer baru bekerja di masa depan. Jadi akan lebih mudah baginya untuk mencari tahu apa yang terjadi di dalam barisan kode. Semua orang mungkin tidak mengetahui optimasi seperti itu.sumber
Saya mengatakan untuk tujuan kompetisi pemrograman. Umumnya mereka memiliki input yang sangat besar di mana pembagian oleh 2 terjadi berkali-kali dan diketahui bahwa inputnya positif atau negatif.
x >> 1 akan lebih baik dari x / 2. Saya memeriksa ideone.com dengan menjalankan program di mana lebih dari 10 ^ 10 divisi dengan 2 operasi berlangsung. x / 2 mengambil hampir 5,5 sedangkan x >> 1 mengambil hampir 2,6 untuk program yang sama.
sumber
x/2
untukx>>1
. Untuk nilai yang ditandatangani, hampir semua implementasi mendefinisikanx>>1
memiliki arti yang setara denganx/2
tetapi dapat dihitung lebih cepat ketikax
positif, dan bermanfaat berbeda darix/2
ketikax
negatif.Saya akan mengatakan ada beberapa hal yang perlu dipertimbangkan.
Bitshift harus lebih cepat, karena tidak ada perhitungan khusus yang benar-benar diperlukan untuk menggeser bit, namun seperti yang ditunjukkan, ada masalah potensial dengan angka negatif. Jika Anda dipastikan memiliki angka positif, dan sedang mencari kecepatan maka saya akan merekomendasikan bitshift.
Operator divisi sangat mudah bagi manusia untuk membaca. Jadi jika Anda mencari pembacaan kode, Anda bisa menggunakan ini. Perhatikan bahwa bidang optimisasi kompiler telah jauh, sehingga membuat kode mudah dibaca dan dipahami adalah praktik yang baik.
Jika Anda mengejar kinerja murni, saya akan merekomendasikan membuat beberapa tes yang dapat melakukan operasi jutaan kali. Contoh eksekusi beberapa kali (ukuran sampel Anda) untuk menentukan mana yang terbaik secara statistik dengan OS / Perangkat Keras / Kompiler / Kode Anda.
sumber
>>
dan tidak cocok dengan apa/
.Sejauh menyangkut CPU, operasi bit-shift lebih cepat daripada operasi divisi. Namun, kompiler mengetahui hal ini dan akan mengoptimalkan secara tepat sejauh mungkin, sehingga Anda dapat membuat kode dengan cara yang paling masuk akal dan mudah mengetahui bahwa kode Anda berjalan secara efisien. Tetapi ingat bahwa suatu
unsigned int
kaleng (dalam beberapa kasus) dapat dioptimalkan lebih baik daripada suatuint
karena alasan yang disebutkan sebelumnya. Jika Anda tidak perlu masuk aritmatika, maka jangan sertakan bit tanda.sumber
x = x / 2; adalah kode yang cocok untuk digunakan .. tetapi operasi tergantung pada program Anda sendiri tentang bagaimana output yang ingin Anda hasilkan.
sumber
Jadikan niat Anda lebih jelas ... misalnya, jika Anda ingin membagi, gunakan x / 2, dan biarkan kompilator mengoptimalkannya untuk menggeser operator (atau apa pun).
Prosesor hari ini tidak akan membiarkan pengoptimalan ini berdampak pada kinerja program Anda.
sumber
Jawabannya tergantung pada lingkungan tempat Anda bekerja.
x /= 2
menjadix >>= 1
, kehadiran simbol divisi akan menaikkan lebih banyak alis di lingkungan itu daripada menggunakan shift untuk melakukan pembagian.x >>= 1
dengan komentar yang menjelaskan alasannya mungkin yang terbaik hanya untuk kejelasan tujuan.x /= 2
. Lebih baik menyelamatkan programmer berikutnya yang kebetulan melihat kode Anda 10 detik untuk operasi shift Anda daripada membuktikan bahwa Anda tahu perubahan itu lebih efisien daripada optimasi kompiler.Semua ini mengasumsikan bilangan bulat yang tidak ditandatangani. Pergeseran sederhana mungkin bukan yang Anda inginkan untuk ditandatangani. Juga, DanielH membawa poin bagus tentang penggunaan
x *= 0.5
untuk bahasa tertentu seperti ActionScript.sumber
mod 2, tes untuk = 1. tidak tahu sintaks di c. tapi ini mungkin yang tercepat.
sumber
umumnya shift kanan dibagi:
ini kadang-kadang digunakan untuk mempercepat program dengan biaya kejelasan. Saya tidak berpikir Anda harus melakukannya. Kompiler cukup pintar untuk melakukan speedup secara otomatis. Ini berarti bahwa menempatkan perubahan tidak memberi Anda keuntungan apa pun dengan mengorbankan kejelasan .
Lihatlah halaman ini dari Pemrograman C ++ Praktis.
sumber
(x+128)>>8
akan menghitung nilai-nilai yangx
tidak mendekati maksimum, bagaimana seseorang dapat melakukannya dengan cepat tanpa perubahan? Perhatikan bahwa(x+128)/256
itu tidak akan berhasil. Apakah Anda tahu ada ekspresi baik yang akan?Jelas, jika Anda menulis kode Anda untuk orang berikutnya yang membacanya, cari kejelasan "x / 2".
Namun, jika kecepatan adalah tujuan Anda, cobalah baik cara dan waktu hasilnya. Beberapa bulan yang lalu saya mengerjakan sebuah rutin konvolusi bitmap yang melibatkan melangkah melalui array bilangan bulat dan membagi masing-masing elemen dengan 2. Saya melakukan segala macam hal untuk mengoptimalkannya termasuk trik lama untuk mengganti "x >> 1" untuk "x / 2 ".
Ketika saya benar-benar menghitung waktu kedua cara saya menemukan saya terkejut bahwa x / 2 lebih cepat dari x >> 1
Ini menggunakan Microsoft VS2008 C ++ dengan optimasi default dihidupkan.
sumber
Dari segi kinerja. Operasi shift CPU secara signifikan lebih cepat daripada membagi kode-op. Jadi membagi dengan dua atau mengalikan dengan 2 dll semua mendapat manfaat dari operasi shift.
Seperti untuk tampilan dan nuansa. Sebagai insinyur kapan kita menjadi begitu terikat pada kosmetik yang bahkan wanita cantik tidak gunakan! :)
sumber
X / Y adalah yang benar ... dan operator pemindahan ">>" ..jika kita ingin dua membagi bilangan bulat kita dapat menggunakan (/) operator dividen. operator shift digunakan untuk menggeser bit ..
x = x / 2; x / = 2; bisa kita gunakan seperti ini ..
sumber
Sementara x >> 1 lebih cepat dari x / 2, penggunaan yang tepat >> ketika berhadapan dengan nilai negatif sedikit lebih rumit. Dibutuhkan sesuatu yang mirip dengan yang berikut:
sumber