Opsi mana yang lebih baik untuk digunakan untuk membagi angka integer dengan 2?


406

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 xini adalah bilangan bulat.


75
Jika Anda benar-benar ingin menetapkan hasil xlagi, tidak ada yang sesuai dengan cara ini: itu harus berupa x >>= 1atau x /= 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.
leftaroundtentang

33
Saya tidak setuju dengan leftaroundabout. - Tapi saya pikir patut dicatat bahwa ada operasi yang disebut pergeseran aritmatika dalam banyak bahasa pemrograman yang menjaga tanda bit di tempatnya dan karenanya bekerja untuk nilai yang ditandatangani seperti yang diharapkan. Sintaksnya mungkin seperti 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.
JimmyB

36
Saya lebih suka x /= 2karena x >>= 1terlihat terlalu mirip monadic bind;)
fredoverflow

19
@leftaroundabout - Saya hanya menganggapnya lebih mudah dibaca x = x / 2daripada ditulis x /= 2. Preferensi subjektif mungkin :)
JimmyB

8
@HannoBinder: pasti subyektif, khususnya banyak kebiasaan. IMO, dalam bahasa di mana semua operator aritmatika memiliki ⬜=kombinasinya, ini harus digunakan kapan pun memungkinkan. Ini akan menghapus kebisingan dan menempatkan penekanan pada fakta bahwa xyang 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.
leftaround sekitar

Jawaban:


847

Gunakan operasi yang paling menggambarkan apa yang Anda coba lakukan.

  • Jika Anda memperlakukan nomor sebagai urutan bit, gunakan bithift.
  • Jika Anda memperlakukannya sebagai nilai numerik, gunakan pembagian.

Perhatikan bahwa mereka tidak persis setara. Mereka dapat memberikan hasil yang berbeda untuk bilangan bulat negatif. Sebagai contoh:

-5 / 2  = -2
-5 >> 1 = -3

(ideone)


20
Pertanyaan aslinya juga tidak jelas tentang istilah 'terbaik'. 'Terbaik' dalam hal kecepatan, keterbacaan, pertanyaan ujian untuk mengelabui siswa, dll ... Dengan tidak adanya penjelasan tentang apa arti 'terbaik', ini tampaknya menjadi jawaban yang paling benar.
Ray

47
Dalam C ++ 03, keduanya implementasi didefinisikan untuk angka negatif, dan mungkin memberikan hasil yang sama. Dalam C ++ 11, pembagian didefinisikan dengan baik untuk angka negatif, tetapi pergeseran masih implementasi yang didefinisikan.
James Kanze

2
Sementara definisi / implementasi (apakah jika dibulatkan ke atas atau ke bawah untuk angka negatif) didefinisikan dalam standar C awal. Itu harus selalu konsisten dengan% (modulo / operator sisa).
ctrl-alt-delor

7
"Implementasi didefinisikan" berarti bahwa pelaksana kompiler harus memilih di antara beberapa pilihan implementasi, biasanya dengan kendala substansial. Di sini, satu kendala adalah bahwa %dan /operator harus konsisten untuk operan positif dan negatif sehingga (a/b)*b+(a%b)==abenar terlepas dari tanda-tanda adan b. Biasanya, penulis akan membuat pilihan yang mendapatkan kinerja terbaik dari CPU.
RBerteig

6
Jadi semua orang yang mengatakan "kompiler akan mengubahnya menjadi shift" salah, kan? Kecuali jika kompiler dapat menjamin bahwa Anda berurusan dengan bilangan bulat non-negatif (baik itu konstanta atau int unsigned), ia tidak dapat mengubahnya menjadi shift
Kip

225

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.


15
Banyak kompiler tidak akan mengubah pembagian dengan kekuatan dua menjadi bithift. Itu akan menjadi optimasi yang salah untuk bilangan bulat yang ditandatangani. Anda harus mencoba melihat output perakitan dari kompiler Anda dan lihat sendiri.
exDM69

1
IIRC I menggunakannya untuk membuat pengurangan paralel lebih cepat pada CUDA (hindari integer div). Namun ini lebih dari setahun yang lalu, saya bertanya-tanya seberapa pintar kompiler CUDA saat ini.
Nils

9
@ exDM69: Banyak kompiler akan melakukannya bahkan untuk bilangan bulat yang ditandatangani, dan hanya menyesuaikannya sesuai dengan ketandatanganan. Alat yang bagus untuk bermain dengan hal-hal ini adalah ini: tinyurl.com/6uww253
PlasmaHH

19
@ exDM69: Dan itu relevan, bagaimana? Saya berkata "jika mungkin", tidak "selalu". Jika optimasi salah, maka melakukannya secara manual tidak membuatnya benar (ditambah seperti yang disebutkan, GCC cukup pintar untuk mengetahui penggantian yang tepat untuk bilangan bulat yang ditandatangani).
Cat Plus Plus

4
Melihat halaman WikiPedia, ini tampaknya kontroversial, tetapi saya tidak akan menyebutnya pengurangan kekuatan. Pengurangan kekuatan adalah ketika, dalam satu lingkaran, Anda mengurangi dari, misalnya, perkalian dengan penambahan, dengan menambahkan nilai sebelumnya dalam loop. Ini lebih merupakan optimasi lubang intip, yang dapat dilakukan oleh kompiler dengan cukup andal.
SomeCallMeTim

189

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:

    x = x / 2 + 5;
    x = x >> 1 + 5;  // not the same as above
  • 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.


5
Perlu juga ditambahkan bahwa meskipun ada aturan prioritas, tidak ada salahnya menggunakan kurung. Sementara memperbaiki beberapa kode produksi, saya benar-benar melihat sesuatu dari bentuk a/b/c*d(di mana a..ddilambangkan variabel numerik) alih-alih jauh lebih mudah dibaca (a*d)/(b*c).

1
Kinerja dan optimalisasi tergantung pada kompiler dan target. Sebagai contoh, saya melakukan beberapa pekerjaan untuk mikrokontroler di mana sesuatu yang lebih tinggi dari -O0 dinonaktifkan kecuali Anda membeli kompiler komersial, sehingga kompiler tidak akan mengubah membagi menjadi bithifts. Selanjutnya, bitshift mengambil satu siklus dan membagi membutuhkan 18 siklus pada target ini dan karena kecepatan clock mikrokontroler sangat rendah, ini mungkin memang menjadi hit kinerja yang terlihat (tetapi tergantung pada kode Anda - Anda harus menggunakan / sampai profiling memberi tahu Anda ini masalah!)

4
@JackManey, jika ada kemungkinan a*datau b*cakan menghasilkan overflow, formulir yang kurang dapat dibaca tidak setara dan memiliki keuntungan yang jelas. PS Saya setuju bahwa tanda kurung adalah teman baik Anda.
Mark Ransom

@ Markarkh - Titik adil (meskipun saya berlari ke a/b/c*ddalam 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).

Kode x=x/2;ini hanya "lebih jelas" daripada x>>=1jika xtidak akan pernah menjadi angka negatif ganjil atau orang tidak peduli dengan kesalahan satu per satu. Kalau tidak x=x/2;dan x>>=1;memiliki arti yang berbeda. Jika yang dibutuhkan seseorang adalah nilai yang dihitung x>>=1, saya akan menganggapnya lebih jelas daripada x = (x & ~1)/2atau x = (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 oleh x/=2, itu lebih jelas daripada ((x + ((unsigned)x>>31)>>1).
supercat

62

Yang mana merupakan opsi terbaik dan mengapa untuk membagi angka integer dengan 2?

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.


58

Cukup gunakan divide ( /), anggap itu lebih jelas. Kompiler akan mengoptimalkannya.


34
Kompilator harus mengoptimalkannya.
Noctis Skytower

12
Jika kompiler tidak mengoptimalkannya, Anda harus menggunakan kompiler yang lebih baik.
David Stone

3
@DavidStone: Pada apa prosesor dapat divisi compiler mengoptimalkan dari bilangan bulat mungkin negatif ditandatangani oleh konstan selain 1 untuk menjadi seefisien pergeseran?
supercat

1
@supercat: Itu poin yang bagus. Anda tentu saja dapat menyimpan nilai dalam bilangan bulat yang tidak ditandatangani (yang saya rasa memiliki reputasi yang jauh lebih buruk daripada seharusnya ketika dikombinasikan dengan peringatan ketidakcocokan yang ditandatangani / tidak ditandatangani), dan sebagian besar kompiler juga memiliki cara untuk mengatakan kepada mereka untuk menganggap sesuatu itu benar ketika mengoptimalkan . Saya lebih suka membungkus itu dalam makro kompatibilitas dan memiliki sesuatu seperti di ASSUME(x >= 0); x /= 2;atas x >>= 1;, tapi itu masih poin penting untuk diangkat.
David Stone

39

Saya setuju dengan jawaban lain yang harus Anda sukai x / 2karena maksudnya lebih jelas, dan kompiler harus mengoptimalkannya untuk Anda.

Namun, alasan lain untuk memilih x / 2lebih x >> 1adalah bahwa perilaku >>adalah implementasi tergantung jika xadalah ditandatangani intdan negatif.

Dari bagian 6.5.7, bullet 5 dari standar ISO C99:

Hasil E1 >> E2ini E1benar-bergeser E2posisi bit. Jika E1memiliki tipe yang tidak ditandatangani atau jika E1memiliki tipe yang ditandatangani dan nilai yang tidak negatif, nilai hasilnya adalah bagian integral dari hasil bagi E1/ 2 E2. Jika E1memiliki tipe yang ditandatangani dan nilai negatif, nilai yang dihasilkan ditentukan oleh implementasi.


3
Penting untuk dicatat bahwa perilaku yang didefinisikan oleh banyak implementasi untuk x>>scalepowerbilangan negatif akan tepat apa yang diperlukan ketika membagi nilai dengan kekuatan dua untuk tujuan seperti rendering layar, ketika menggunakan x/scalefactorakan salah kecuali jika seseorang menerapkan koreksi ke nilai negatif.
supercat

32

x / 2lebih jelas, dan x >> 1tidak 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 dikonversi x / 2ke x >> 1jika mereka tahu nomornya tidak boleh negatif (bahkan saya pikir saya tidak dapat memverifikasi ini).

Bahkan x / 2mungkin tidak menggunakan instruksi CPU divisi (lambat), karena beberapa pintasan dimungkinkan , tetapi masih lebih lambat dari itu x >> 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) >>> 1akan mengembalikan nilai rata-rata bahkan untuk nilai yang sangat besar dari adan b. 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) / 2untuk menghitung rata-rata. tidak berfungsi dengan benar. Solusi yang tepat adalah dengan menggunakan (a + b) >>> 1.)


1
Compiler tidak dapat dikonversi x/2ke x>>1dalam kasus di mana xmungkin negatif. Jika yang diinginkan adalah nilai yang x>>1akan dihitung, itu hampir pasti akan lebih cepat daripada ungkapan apa pun yang melibatkan x/2nilai yang sama.
supercat

Kamu benar. Sebuah kompiler hanya dapat dikonversi x/2ke x>>1jika dia tahu nilainya tidak negatif. Saya akan mencoba memperbarui jawaban saya.
Thomas Mueller

kompiler masih menghindari divinstruksi, dengan mengkonversi x/2ke (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/4F8Ms4
Peter Cordes

Pertanyaan ini ditandai sebagai C dan C ++.
Josh Sanford

22

Knuth berkata:

Optimalisasi prematur adalah akar dari semua kejahatan.

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.


4
Apa yang Anda anggap metode yang disukai untuk menurunkan angka dengan kekuatan dua jika seseorang ingin bilangan bulat untuk mempertahankan aksioma (yang berlaku untuk bilangan asli dan bilangan real) yang (n + d) / d = (n / d) + 1? Pelanggaran yang menyebabkan aksioma saat penskalaan grafik akan menyebabkan "jahitan" terlihat di hasilnya. Jika seseorang menginginkan sesuatu yang seragam dan hampir simetris tentang nol, (n+8)>>4berfungsi dengan baik. Bisakah Anda menawarkan pendekatan apa pun yang sejelas atau seefisien tanpa menggunakan shift kanan yang sudah ditandatangani?
supercat

19

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 sarlinstruksi (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

int div2signed(int a) {
  return a / 2;
}

dan ini mengkompilasi ke

    movl    %edi, %eax
    shrl    $31, %eax
    addl    %edi, %eax
    sarl    %eax
    ret

sama untuk shift

int shr2signed(int a) {
  return a >> 1;
}

dengan output:

    sarl    %edi
    movl    %edi, %eax
    ret

Bergantung pada apa yang dilakukan seseorang, itu dapat memperbaiki kesalahan off-by-one, atau dapat menyebabkan kesalahan off-by-one (dibandingkan dengan apa yang sebenarnya dibutuhkan) yang akan mengharuskan penggunaan kode lebih lanjut untuk memperbaikinya. Jika yang diinginkan seseorang adalah hasil yang membuahkan hasil, pergantian-kanan lebih cepat dan lebih mudah daripada alternatif apa pun yang saya tahu.
supercat

Jika Anda membutuhkan lantai, tidak mungkin Anda akan menggambarkan apa yang Anda inginkan sebagai "dibagi dengan 2"
Michael Donohue

Pembagian bilangan asli dan bilangan real menjunjung tinggi aksioma bahwa (n + d) / d = (n / d) +1. Pembagian bilangan real juga menjunjung tinggi (-n) / d = - (n / d), suatu aksioma yang tidak ada artinya dengan bilangan asli. Tidak mungkin untuk memiliki operator divisi yang ditutup pada bilangan bulat dan menjunjung tinggi kedua aksioma. Dalam pikiran saya, mengatakan bahwa aksioma pertama harus berlaku untuk semua bilangan dan yang kedua hanya untuk real tampaknya lebih alami daripada mengatakan bahwa yang pertama harus berlaku untuk bilangan bulat atau nyata tetapi tidak untuk bilangan bulat. Lebih lanjut, saya ingin tahu dalam kasus apa aksioma kedua sebenarnya berguna .
supercat

1
Metode pembagian integer yang memenuhi aksioma pertama akan mempartisi garis bilangan menjadi wilayah ukuran 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 ukurannya d, tetapi salah satunya adalah ukuran 2*d-1. Tidak persis divisi "sama". Bisakah Anda menawarkan saran kapan partisi eksentball sebenarnya berguna?
supercat

Output kompiler Anda untuk shr2signed salah. gcc pada x86 memilih untuk mengimplementasikan >> bilangan bulat yang ditandatangani dengan pergeseran aritmatika ( 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.
Peter Cordes

15

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.


2
@minitech: Itu ujian yang sangat buruk. Semua kode dalam tes ini konstan. Bahkan sebelum kode JITed, itu akan menghilangkan semua konstanta.

@ M28: Saya cukup yakin internal jsPerf (yaitu eval) membuat itu terjadi lagi setiap kali. Apapun, ya, ini adalah tes yang sangat buruk, karena ini adalah optimasi yang sangat konyol.
Ry-

13

Gunakan x = x / 2; ATAU x /= 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.


12

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.


1
Untuk nilai-nilai unsigned, compiler harus mengoptimalkan x/2untuk x>>1. Untuk nilai yang ditandatangani, hampir semua implementasi mendefinisikan x>>1memiliki arti yang setara dengan x/2tetapi dapat dihitung lebih cepat ketika xpositif, dan bermanfaat berbeda dari x/2ketika xnegatif.
supercat

12

Saya akan mengatakan ada beberapa hal yang perlu dipertimbangkan.

  1. 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.

  2. 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.

  3. Tergantung pada perangkat keras yang mendasarinya, operasi mungkin memiliki kecepatan yang berbeda. Hukum Amdal adalah untuk membuat kasus umum cepat. Jadi, Anda mungkin memiliki perangkat keras yang dapat melakukan berbagai operasi lebih cepat daripada yang lain. Misalnya, mengalikan dengan 0,5 mungkin lebih cepat daripada membagi dengan 2. (Memang Anda mungkin perlu mengambil dasar perkalian jika Anda ingin menegakkan pembagian bilangan bulat).

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.


2
"Bitshift harusnya lebih cepat". compiler akan mengoptimalkan divisi menjadi bitshifts
Trevor Hickey

Saya harap mereka akan melakukannya, tetapi kecuali jika Anda memiliki akses ke sumber kompiler, Anda tidak dapat memastikan :)
James Oravec

1
Saya juga akan merekomendasikan bitshift jika implementasi seseorang menanganinya dengan cara yang paling umum dan cara seseorang ingin menangani angka negatif cocok dengan apa yang cocok >>dan tidak cocok dengan apa /.
supercat

12

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 intkaleng (dalam beberapa kasus) dapat dioptimalkan lebih baik daripada suatu intkarena alasan yang disebutkan sebelumnya. Jika Anda tidak perlu masuk aritmatika, maka jangan sertakan bit tanda.


11

x = x / 2; adalah kode yang cocok untuk digunakan .. tetapi operasi tergantung pada program Anda sendiri tentang bagaimana output yang ingin Anda hasilkan.


11

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.


10

Jawabannya tergantung pada lingkungan tempat Anda bekerja.

  • Jika Anda bekerja pada mikrokontroler 8-bit atau apa pun tanpa dukungan perangkat keras untuk penggandaan, diharapkan pergeseran bit dan hal biasa, dan sementara kompiler hampir pasti akan berubah x /= 2menjadi x >>= 1, kehadiran simbol divisi akan menaikkan lebih banyak alis di lingkungan itu daripada menggunakan shift untuk melakukan pembagian.
  • Jika Anda bekerja di lingkungan yang kritis terhadap kinerja atau bagian kode, atau kode Anda dapat dikompilasi dengan optimisasi kompiler dimatikan, x >>= 1dengan komentar yang menjelaskan alasannya mungkin yang terbaik hanya untuk kejelasan tujuan.
  • Jika Anda tidak berada di bawah salah satu kondisi di atas, buat kode Anda lebih mudah dibaca hanya dengan menggunakan 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.5untuk bahasa tertentu seperti ActionScript.


8

mod 2, tes untuk = 1. tidak tahu sintaks di c. tapi ini mungkin yang tercepat.


7

umumnya shift kanan dibagi:

q = i >> n; is the same as: q = i / 2**n;

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.


Jika seseorang ingin menghitung nilai yang misalnya (x+128)>>8akan menghitung nilai-nilai yang xtidak mendekati maksimum, bagaimana seseorang dapat melakukannya dengan cepat tanpa perubahan? Perhatikan bahwa (x+128)/256itu tidak akan berhasil. Apakah Anda tahu ada ekspresi baik yang akan?
supercat

7

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.


4

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! :)


3

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 ..


0

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:

// Extension Method
public static class Global {
    public static int ShiftDivBy2(this int x) {
        return (x < 0 ? x + 1 : x) >> 1;
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.