Apa algoritma yang paling efisien untuk mencapai hal berikut:
0010 0000 => 0000 0100
Konversi dari MSB-> LSB ke LSB-> MSB. Semua bit harus dibalik; yaitu, ini bukan pertukaran endianness.
c
algorithm
bit-manipulation
green_t
sumber
sumber
Jawaban:
CATATAN : Semua algoritma di bawah ini dalam C, tetapi harus portabel untuk bahasa pilihan Anda (jangan lihat saya ketika mereka tidak secepat :)
Pilihan
Memori Rendah (32-bit
int
, mesin 32-bit) (dari sini ):Dari halaman Bit Twiddling Hacks yang terkenal :
Tercepat (tabel pencarian) :
Anda dapat memperluas ide ini menjadi 64-bit
int
, atau menukar memori untuk kecepatan (dengan asumsi L1 Data Cache Anda cukup besar), dan membalikkan 16 bit sekaligus dengan tabel pencarian entri 64K.Lainnya
Sederhana
Lebih cepat (prosesor 32-bit)
Lebih cepat (prosesor 64-bit)
Jika Anda ingin melakukan ini pada 32-bit
int
, cukup membalikkan bit di setiap byte, dan membalikkan urutan byte. Itu adalah:Hasil
Saya membandingkan dua solusi yang paling menjanjikan, tabel pencarian, dan bitwise-AND (yang pertama). Mesin uji adalah laptop w / 4GB DDR2-800 dan Core 2 Duo T7500 @ 2.4GHz, 4MB L2 Cache; YMMV. Saya menggunakan gcc 4.3.2 di Linux 64-bit. OpenMP (dan binding GCC) digunakan untuk timer resolusi tinggi.
mundur.c
reverse_lookup.c
Saya mencoba kedua pendekatan pada beberapa optimasi yang berbeda, menjalankan 3 percobaan di setiap level, dan setiap percobaan membalikkan 100 juta acak
unsigned ints
. Untuk opsi tabel pencarian, saya mencoba kedua skema (opsi 1 dan 2) yang diberikan pada halaman retas bitwise. Hasilnya ditunjukkan di bawah ini.Bitwise DAN
Tabel Pencarian (opsi 1)
Tabel Pencarian (opsi 2)
Kesimpulan
Gunakan tabel pencarian, dengan opsi 1 (pengalamatan byte tidak terlalu lambat) jika Anda mengkhawatirkan kinerja. Jika Anda perlu memeras setiap byte terakhir memori dari sistem Anda (dan Anda mungkin, jika Anda peduli dengan kinerja pembalikan bit), versi yang dioptimalkan dari pendekatan bitwise-AND juga tidak terlalu buruk.
Peringatan
Ya, saya tahu kode benchmark adalah hack lengkap. Saran tentang cara memperbaikinya lebih dari disambut. Hal-hal yang saya ketahui tentang:
ld
meledak dengan beberapa kesalahan redefinisi simbol gila), jadi saya tidak percaya kode yang dihasilkan disetel untuk mikroarsitektur saya.32-bit
EDIT: Saya juga mencoba menggunakan
uint64_t
jenis pada mesin saya untuk melihat apakah ada peningkatan kinerja. Kinerja sekitar 10% lebih cepat dari 32-bit, dan hampir identik apakah Anda hanya menggunakan tipe 64-bit untuk membalikkan bit pada duaint
tipe 32-bit sekaligus, atau apakah Anda benar-benar membalikkan bit menjadi dua kali lipat 64- nilai bit. Kode perakitan ditunjukkan di bawah ini (untuk kasus sebelumnya, membalikkan bit untuk duaint
jenis 32-bit sekaligus):sumber
Utas ini menarik perhatian saya karena berurusan dengan masalah sederhana yang membutuhkan banyak pekerjaan (siklus CPU) bahkan untuk CPU modern. Dan suatu hari saya juga berdiri di sana dengan masalah ¤ #% "#" yang sama. Saya harus membalik jutaan byte. Namun saya tahu semua sistem target saya berbasis Intel modern, jadi mari kita mulai mengoptimalkan secara ekstrim !!!
Jadi saya menggunakan kode pencarian Matt J sebagai basis. sistem yang saya benchmarking adalah i7 haswell 4700eq.
Pencarian Matt J bitflipping 400 000 000 byte: Sekitar 0,272 detik.
Saya kemudian melanjutkan dan mencoba melihat apakah kompiler ISPC Intel dapat membuat vektor aritmatika secara terbalik. C.
Saya tidak akan membuat Anda bosan dengan temuan saya di sini karena saya mencoba banyak untuk membantu kompiler menemukan hal-hal, bagaimanapun saya berakhir dengan kinerja sekitar 0,15 detik untuk bitflip 400.000 000 byte. Ini pengurangan yang bagus tapi untuk aplikasi saya itu masih terlalu lambat ..
Jadi orang-orang membiarkan saya menyajikan bitflipper berbasis Intel tercepat di dunia. Jam di:
Waktu untuk bitflip 400000000 byte: 0,050082 detik !!!!!
Printf adalah untuk debugging ..
Di sini adalah pekerja keras:
Kode ini mengambil 32 byte kemudian menutup keluar camilan. Menggigit tinggi akan bergeser ke kanan dengan 4. Kemudian saya menggunakan vpshufb dan ymm4 / ymm3 sebagai tabel pencarian. Saya bisa menggunakan tabel pencarian tunggal tetapi kemudian saya harus bergeser ke kiri sebelum ATAU menggigit bersama-sama lagi.
Bahkan ada cara yang lebih cepat untuk membalik bit. Tapi saya terikat utas dan CPU jadi ini adalah tercepat yang bisa saya capai. Bisakah Anda membuat versi yang lebih cepat?
Harap tidak membuat komentar tentang menggunakan perintah Intel C / C ++ Compiler Intrinsic Equivalent ...
sumber
pshub
, karena lagipula popcount terbaik juga dilakukan! Saya akan menulisnya di sini jika bukan untuk Anda. Pujian.popcnt
,,tzcnt
danpext
semuanya pada port 1. Jadi setiappext
atautzcnt
biayapopcnt
throughput Anda. Jika data Anda panas di cache L1D, cara tercepat untuk popcount array di Intel CPU adalah dengan AVX2 pshufb. (Ryzen memilikipopcnt
throughput 4 per jam sehingga mungkin optimal, tetapi Bulldozer-keluarga memiliki satupopcnt r64,r64
throughput 4 jam ... agner.org/optimize ).Ini adalah solusi lain untuk orang yang suka rekursi.
Idenya sederhana. Membagi input menjadi setengah dan menukar kedua bagian, terus sampai mencapai bit tunggal.
Berikut adalah fungsi rekursif untuk menyelesaikannya. (Catatan Saya telah menggunakan int unsigned, sehingga dapat bekerja untuk input hingga sizeof (unsigned int) * 8 bit.
Ini hasilnya:
sumber
numBits
int, ketika Anda membagi 3 dengan 2 untuk param fungsi itu akan dibulatkan menjadi 1?Yah ini tentu tidak akan menjadi jawaban seperti Matt J tetapi semoga tetap bermanfaat.
Ini persis ide yang sama dengan algoritma Matt terbaik kecuali bahwa ada instruksi kecil ini disebut BSWAP yang menukar byte (bukan bit) dari angka 64-bit. Jadi b7, b6, b5, b4, b3, b2, b1, b0 menjadi b0, b1, b2, b3, b3, b4, b5, b6, b7. Karena kami bekerja dengan nomor 32-bit, kami perlu menggeser nomor byte-swapped kami menjadi 32 bit. Ini hanya meninggalkan kita dengan tugas menukar 8 bit setiap byte yang dilakukan dan voila! dilakukan.
Pengaturan waktu: pada mesin saya, algoritma Matt berjalan dalam ~ 0,52 detik per percobaan. Milik saya berlari dalam sekitar 0,42 detik per percobaan. 20% lebih cepat tidak buruk saya pikir.
Jika Anda khawatir tentang ketersediaan instruksi, BSWAP Wikipedia mencantumkan instruksi BSWAP yang ditambahkan dengan 80846 yang keluar pada tahun 1989. Perlu dicatat bahwa Wikipedia juga menyatakan bahwa instruksi ini hanya bekerja pada register 32 bit yang jelas bukan kasus di komputer saya, itu sangat berfungsi hanya pada register 64-bit.
Metode ini akan bekerja dengan baik untuk semua tipe data integral sehingga metode ini dapat digeneralisasi secara sepele dengan mengirimkan jumlah byte yang diinginkan:
yang kemudian bisa disebut seperti:
Kompiler harus dapat mengoptimalkan parameter tambahan (dengan asumsi kompiler menguraikan fungsi) dan untuk
sizeof(size_t)
kasus ini pergeseran kanan akan dihapus sepenuhnya. Perhatikan bahwa setidaknya GCC tidak dapat menghapus BSWAP dan shift kanan jika dilewatisizeof(char)
.sumber
unsigned long long int
yang harus paling tidak 64 bit, sesuai di sini dan di siniJawaban Anders Cedronius memberikan solusi hebat bagi orang-orang yang memiliki CPU x86 dengan dukungan AVX2. Untuk platform x86 tanpa dukungan AVX atau platform non-x86, salah satu dari implementasi berikut ini akan berfungsi dengan baik.
Kode pertama adalah varian dari metode partisi biner klasik, dikodekan untuk memaksimalkan penggunaan idiom shift-plus-logic yang berguna pada berbagai prosesor ARM. Selain itu, ia menggunakan pembuatan on-the-fly mask yang dapat bermanfaat bagi prosesor RISC yang jika tidak memerlukan banyak instruksi untuk memuat setiap nilai mask 32-bit. Compiler untuk platform x86 harus menggunakan propagasi konstan untuk menghitung semua masker pada waktu kompilasi daripada waktu berjalan.
Dalam volume 4A "The Art of Computer Programming", D. Knuth menunjukkan cara-cara cerdas membalikkan bit yang agak mengejutkan membutuhkan operasi lebih sedikit daripada algoritma partisi biner klasik. Salah satu algoritma untuk operan 32-bit, yang tidak dapat saya temukan di TAOCP, ditunjukkan dalam dokumen ini di situs web Hacker's Delight.
Menggunakan kompiler Intel C / C ++ kompiler 13.1.3.198, kedua fungsi di atas secara otomatis meng-vektor-
XMM
register register sasaran dengan baik . Mereka juga bisa di-vektor-kan secara manual tanpa banyak usaha.Pada IvyBridge Xeon E3 1270v2 saya, menggunakan kode vektor otomatis, 100 juta
uint32_t
kata dibalik dalam 0,070 detik menggunakanbrev_classic()
, dan 0,068 detik menggunakanbrev_knuth()
. Saya berhati-hati untuk memastikan bahwa tolok ukur saya tidak dibatasi oleh bandwidth memori sistem.sumber
brev_knuth()
? Atribusi dalam PDF dari Hacker's Delight tampaknya menunjukkan bahwa angka-angka ini langsung dari Knuth sendiri. Saya tidak bisa mengklaim telah memahami deskripsi Knuth tentang prinsip-prinsip desain yang mendasari dalam TAOCP cukup untuk menjelaskan bagaimana konstanta diturunkan, atau bagaimana seseorang akan pergi tentang konstanta yang berasal dan faktor pergeseran untuk ukuran kata yang sewenang-wenang.Anggap Anda memiliki array bit, bagaimana dengan ini: 1. Mulai dari MSB, dorong bit ke tumpukan satu per satu. 2. Pop bit dari tumpukan ini ke array lain (atau array yang sama jika Anda ingin menghemat ruang), menempatkan bit pertama yang muncul ke dalam MSB dan melanjutkan ke bit yang kurang signifikan dari sana.
sumber
Instruksi ARM asli "rbit" dapat melakukannya dengan 1 siklus CPU dan 1 register CPU ekstra, tidak mungkin dikalahkan.
sumber
Ini bukan pekerjaan untuk manusia! ... tapi sempurna untuk sebuah mesin
Ini tahun 2015, 6 tahun sejak pertanyaan ini pertama kali diajukan. Kompiler sejak itu menjadi tuan kita, dan tugas kita sebagai manusia hanyalah membantu mereka. Jadi apa cara terbaik untuk memberikan niat kami pada mesin?
Pembalikan bit sangat umum sehingga Anda harus bertanya-tanya mengapa ISA x86 yang terus berkembang tidak termasuk instruksi untuk melakukannya sekali jalan.
Alasannya: jika Anda memberikan maksud ringkas sebenarnya Anda ke kompiler, pembalikan bit hanya akan memakan waktu ~ 20 siklus CPU . Biarkan saya menunjukkan kepada Anda bagaimana membuat reverse () dan menggunakannya:
Mengkompilasi program sampel ini dengan versi Dentang> = 3,6, -O3, -march = asli (diuji dengan Haswell), memberikan kode kualitas karya seni menggunakan instruksi AVX2 baru, dengan runtime pemrosesan 11 detik ~ 1 miliar mundur () s. Itu ~ 10 ns per mundur (), dengan siklus CPU .5 ns dengan asumsi 2 GHz menempatkan kita pada siklus CPU 20 yang manis.
Peringatan: kode sampel ini harus berlaku sebagai patokan yang layak untuk beberapa tahun, tetapi pada akhirnya akan mulai menunjukkan usia setelah kompiler cukup pintar untuk mengoptimalkan main () untuk hanya mencetak hasil akhir daripada benar-benar menghitung apa pun. Tetapi untuk sekarang ini berfungsi dalam menampilkan reverse ().
sumber
Bit-reversal is so common...
Saya tidak tahu tentang itu. Saya bekerja dengan kode yang berhubungan dengan data pada tingkat bit hampir setiap hari, dan saya tidak ingat pernah memiliki kebutuhan spesifik ini. Dalam skenario apa Anda membutuhkannya? - Bukannya itu bukan masalah yang menarik untuk dipecahkan sendiri.Tentu saja sumber peretasan bit-twiddling yang jelas ada di sini: http://graphics.stanford.edu/~seander/bithacks.html#BitReverseObvious
sumber
Saya tahu itu bukan C tetapi asm:
Ini berfungsi dengan carry bit, sehingga Anda dapat menyimpan flag juga
sumber
rcl
mengalihkan CF kevar1
, bukan hanyashl
yang tidak membaca bendera. (Atauadc dx,dx
). Bahkan dengan perbaikan itu, ini sangat lambat, menggunakanloop
instruksi lambat dan menyimpanvar1
di memori! Sebenarnya saya pikir ini seharusnya menghasilkan output dalam AX, tetapi menyimpan / mengembalikan nilai lama AX di atas hasilnya.Implementasi dengan memori rendah dan tercepat.
sumber
Nah, ini pada dasarnya sama dengan "reverse ()" pertama tetapi 64 bit dan hanya perlu satu mask langsung untuk dimuat dari aliran instruksi. GCC membuat kode tanpa lompatan, jadi ini seharusnya cukup cepat.
sumber
Saya ingin tahu seberapa cepat rotasi mentah yang jelas. Di mesin saya (i7 @ 2600), rata-rata untuk 1.500.150.000 iterasi adalah
27.28 ns
(lebih dari satu set acak 131.071 bilangan bulat 64-bit).Keuntungan: jumlah memori yang dibutuhkan sedikit dan kodenya sederhana. Saya akan mengatakan itu tidak terlalu besar. Waktu yang diperlukan dapat diprediksi dan konstan untuk setiap input (128 operasi aritmatika SHIFT + 64 logis DAN operasi + 64 logis ATAU operasi).
Saya membandingkan waktu terbaik yang diperoleh oleh @Matt J - yang memiliki jawaban yang diterima. Jika saya membaca jawabannya dengan benar, yang terbaik yang didapatnya adalah
0.631739
detik untuk1,000,000
iterasi, yang mengarah ke rata-rata631 ns
per rotasi.Cuplikan kode yang saya gunakan adalah yang di bawah ini:
sumber
Anda mungkin ingin menggunakan pustaka templat standar. Mungkin lebih lambat dari kode yang disebutkan di atas. Namun, bagi saya tampaknya lebih jelas dan mudah dipahami.
sumber
Umum
Kode C Menggunakan input data 1 byte num sebagai contoh.
sumber
Bagaimana dengan yang berikut:
Kecil dan mudah (meskipun, hanya 32 bit).
sumber
Saya pikir ini adalah salah satu cara paling sederhana untuk membalikkan bit. tolong beri tahu saya jika ada kesalahan dalam logika ini. pada dasarnya dalam logika ini, kami memeriksa nilai bit di posisi. atur bit jika nilainya 1 pada posisi terbalik.
sumber
sumber
k
selalu merupakan kekuatan 2, tetapi kompiler mungkin tidak akan membuktikannya dan mengubahnya menjadi bit-scan / shift.Saya pikir metode paling sederhana yang saya tahu berikut.
MSB
adalah input danLSB
output 'terbalik':sumber
sumber
Solusi berbasis loop lain yang keluar dengan cepat ketika jumlahnya rendah (dalam C ++ untuk banyak jenis)
atau dalam C untuk int yang tidak ditandatangani
sumber
Tampaknya banyak posting lain yang peduli tentang kecepatan (yaitu terbaik = tercepat). Bagaimana dengan kesederhanaan? Mempertimbangkan:
dan berharap bahwa kompiler pintar akan mengoptimalkan untuk Anda.
Jika Anda ingin membalikkan daftar bit yang lebih panjang (mengandung
sizeof(char) * n
bit), Anda dapat menggunakan fungsi ini untuk mendapatkan:Ini akan membalikkan [10000000, 10101010] menjadi [01010101, 00000001].
sumber
ith_bit = (c >> i) & 1
. Juga simpan SUB dengan menggeserreversed_char
alih-alih menggeser bit, kecuali Anda berharap itu akan dikompilasi pada x86 kesub something
/bts reg,reg
untuk mengatur bit ke-n dalam register tujuan.Pembalikan bit dalam kode pseudo
source -> byte untuk dibalik tujuan b00101100 -> dibalik, juga harus bertipe unsigned sehingga bit tanda tidak dipropagasi ke bawah
menyalin ke temp sehingga asli tidak terpengaruh, juga harus bertipe unsigned sehingga bit sign tidak digeser secara otomatis
LOOP8: // lakukan tes 8 kali ini jika bytecopy <0 (negatif)
sumber
Solusi sederhana saya
sumber
i
? Juga, apakah konstanta sihir itu* 4
? Apakah ituCHAR_BIT / 2
?Ini untuk 32 bit, kita perlu mengubah ukuran jika kita mempertimbangkan 8 bit.
Membaca bilangan bulat input "num" dalam urutan LSB-> MSB dan menyimpannya di num_reverse dalam urutan MSB-> LSB.
sumber
sumber