8 bit mewakili angka 7 terlihat seperti ini:
00000111
Tiga bit diatur.
Apa algoritma untuk menentukan jumlah bit yang ditetapkan dalam integer 32-bit?
algorithm
binary
bit-manipulation
hammingweight
iec10967
Matt Howells
sumber
sumber
Jawaban:
Ini dikenal sebagai ' Berat Hamming ', 'popcount' atau 'penambahan samping'.
Algoritma 'terbaik' sangat tergantung pada CPU Anda dan apa pola penggunaan Anda.
Beberapa CPU memiliki instruksi built-in tunggal untuk melakukannya dan yang lain memiliki instruksi paralel yang bekerja pada vektor bit. Instruksi paralel (seperti x86
popcnt
, pada CPU yang didukungnya) hampir pasti akan tercepat. Beberapa arsitektur lain mungkin memiliki instruksi yang lambat diimplementasikan dengan loop microcoded yang menguji sedikit per siklus ( kutipan diperlukan ).Metode pencarian tabel pra-populasi bisa sangat cepat jika CPU Anda memiliki cache yang besar dan / atau Anda melakukan banyak instruksi ini dalam satu lingkaran yang ketat. Namun itu dapat menderita karena biaya 'cache miss', di mana CPU harus mengambil beberapa tabel dari memori utama. (Cari setiap byte secara terpisah untuk menjaga tabel tetap kecil.)
Jika Anda tahu bahwa byte Anda sebagian besar adalah 0 atau sebagian besar 1 maka ada algoritma yang sangat efisien untuk skenario ini.
Saya percaya algoritma tujuan umum yang sangat baik adalah sebagai berikut, dikenal sebagai 'paralel' atau 'algoritma SWAR presisi-variabel'. Saya telah menyatakan ini dalam bahasa pseudo seperti-C, Anda mungkin perlu menyesuaikannya agar berfungsi untuk bahasa tertentu (mis. Menggunakan uint32_t untuk C ++ dan >>> di Jawa):
Untuk JavaScript: memaksa untuk integer dengan
|0
untuk kinerja: ubah baris pertama menjadii = (i|0) - ((i >> 1) & 0x55555555);
Ini memiliki perilaku kasus terburuk terbaik dari semua algoritma yang dibahas, jadi akan secara efisien menangani pola penggunaan atau nilai yang Anda berikan.
Bagaimana bithack SWAR ini bekerja:
Langkah pertama adalah versi masking yang dioptimalkan untuk mengisolasi bit aneh / genap, bergeser untuk berbaris, dan menambahkan. Ini secara efektif melakukan 16 penambahan terpisah dalam akumulator 2-bit ( SWAR = SIMD Dalam Daftar ). Seperti
(i & 0x55555555) + ((i>>1) & 0x55555555)
.Langkah selanjutnya mengambil delapan ganjil / genap dari akumulator 16x 2-bit dan menambahkan lagi, menghasilkan jumlah 8x 4-bit. The
i - ...
optimasi tidak mungkin saat ini sehingga tidak hanya topeng sebelum / sesudah pergeseran. Menggunakan0x33...
konstanta yang sama dua kali daripada0xccc...
sebelum bergeser adalah hal yang baik ketika mengkompilasi untuk SPA yang perlu membangun konstanta 32-bit dalam register secara terpisah.Langkah terakhir shift-and-add
(i + (i >> 4)) & 0x0F0F0F0F
melebar ke akumulator 4x 8-bit. Itu topeng setelah menambahkan bukan sebelumnya, karena nilai maksimum dalam akumulator 4-bit adalah4
, jika semua 4 bit dari bit input yang sesuai ditetapkan. 4 + 4 = 8 yang masih muat dalam 4 bit, jadi membawa antar elemen nibble tidak mungkin dilakukani + (i >> 4)
.Sejauh ini ini hanya SIMD yang cukup normal menggunakan teknik SWAR dengan beberapa optimasi pintar. Melanjutkan dengan pola yang sama untuk 2 langkah lagi dapat melebar menjadi 2x 16-bit kemudian 1x 32-bit. Tetapi ada cara yang lebih efisien pada mesin dengan perangkat keras yang berlipat ganda:
Setelah kita memiliki beberapa "elemen" yang cukup, perkalian dengan konstanta sihir dapat menjumlahkan semua elemen menjadi elemen teratas . Dalam hal ini elemen byte. Multiply dilakukan dengan meninggalkan-pergeseran dan menambahkan, jadi kalikan dari
x * 0x01010101
hasil dix + (x<<8) + (x<<16) + (x<<24)
. Elemen 8-bit kami cukup lebar (dan memegang jumlah yang cukup kecil) bahwa ini tidak menghasilkan carry ke atas yang 8 bit.Versi 64-bit ini dapat melakukan elemen 8x 8-bit dalam integer 64-bit dengan pengganda 0x010101010101010101, dan mengekstrak byte tinggi dengan
>>56
. Jadi itu tidak mengambil langkah ekstra, hanya konstanta yang lebih luas. Inilah yang digunakan GCC untuk__builtin_popcountll
sistem x86 ketikapopcnt
instruksi perangkat keras tidak diaktifkan. Jika Anda dapat menggunakan builtin atau intrinsik untuk ini, lakukan itu untuk memberi kompiler kesempatan untuk melakukan optimasi target-spesifik.Dengan SIMD penuh untuk vektor yang lebih luas (mis. Menghitung seluruh array)
Algoritma bitwise-SWAR ini dapat diparalelkan untuk dilakukan dalam beberapa elemen vektor sekaligus, bukan dalam register integer tunggal, untuk mempercepat pada CPU dengan SIMD tetapi tidak ada instruksi popcount yang dapat digunakan. (mis. kode x86-64 yang harus dijalankan pada CPU apa pun, bukan hanya Nehalem atau yang lebih baru.)
Namun, cara terbaik untuk menggunakan instruksi vektor untuk popcount biasanya dengan menggunakan variabel-shuffle untuk melakukan pencarian tabel untuk 4 bit pada setiap byte secara paralel. (4 bit indeks tabel entri 16 diadakan di register vektor).
Pada Intel CPU, perangkat keras 64bit popcnt dapat mengungguli implementasi paralel-bit SSSE3
PSHUFB
sekitar faktor 2, tetapi hanya jika kompiler Anda melakukannya dengan benar . Kalau tidak, SSE dapat keluar secara signifikan di depan. Versi kompiler yang lebih baru menyadari masalah ketergantungan popcnt salah pada Intel .Referensi:
sumber
unsigned int
, untuk dengan mudah menunjukkan bahwa itu bebas dari komplikasi bit tanda. Juga akanuint32_t
lebih aman, seperti pada, Anda mendapatkan apa yang Anda harapkan di semua platform?>>
didefinisikan implementasi untuk nilai-nilai negatif. Argumen perlu diubah (atau dilemparkan) keunsigned
, dan karena kodenya 32-bit-spesifik, mungkin harus menggunakanuint32_t
.Juga pertimbangkan fungsi bawaan kompiler Anda.
Sebagai contoh, pada kompilator GNU Anda bisa menggunakan:
Dalam kasus terburuk kompiler akan menghasilkan panggilan ke suatu fungsi. Dalam kasus terbaik kompiler akan memancarkan instruksi cpu untuk melakukan pekerjaan yang sama lebih cepat.
GCC intrinsik bahkan bekerja di berbagai platform. Popcount akan menjadi arus utama dalam arsitektur x86, jadi masuk akal untuk mulai menggunakan intrinsik sekarang. Arsitektur lain memiliki popcount selama bertahun-tahun.
Pada x86, Anda bisa memberi tahu kompiler bahwa ia dapat menerima dukungan untuk
popcnt
instruksi dengan-mpopcnt
atau-msse4.2
juga mengaktifkan instruksi vektor yang ditambahkan pada generasi yang sama. Lihat opsi GCC x86 .-march=nehalem
(atau-march=
CPU apa pun yang Anda inginkan untuk diasumsikan dan disetel oleh kode Anda) bisa menjadi pilihan yang baik. Menjalankan biner yang dihasilkan pada CPU yang lebih lama akan menghasilkan kesalahan instruksi-ilegal.Untuk membuat binari dioptimalkan untuk mesin tempat Anda membuatnya, gunakan
-march=native
(dengan gcc, dentang, atau ICC).MSVC menyediakan intrinsik untuk
popcnt
instruksi x86 , tetapi tidak seperti gcc, ini benar-benar intrinsik untuk instruksi perangkat keras dan membutuhkan dukungan perangkat keras.Menggunakan
std::bitset<>::count()
bukannya built-inSecara teori, setiap kompiler yang tahu bagaimana cara menghitung uang secara efisien untuk CPU target harus mengekspos fungsi itu melalui ISO C ++
std::bitset<>
. Dalam praktiknya, Anda mungkin lebih baik dengan bit-hack DAN / shift / ADD dalam beberapa kasus untuk beberapa CPU target.Untuk arsitektur target di mana perangkat keras popcount adalah ekstensi opsional (seperti x86), tidak semua kompiler memiliki
std::bitset
yang memanfaatkannya saat tersedia. Misalnya, MSVC tidak memiliki cara untuk mengaktifkanpopcnt
dukungan pada waktu kompilasi, dan selalu menggunakan pencarian tabel , bahkan dengan/Ox /arch:AVX
(yang menyiratkan SSE4.2, meskipun secara teknis ada sedikit fitur terpisah untukpopcnt
.)Tapi setidaknya Anda mendapatkan sesuatu yang portabel yang bekerja di mana-mana, dan dengan gcc / dentang dengan opsi target yang tepat, Anda mendapatkan perangkat keras popcount untuk arsitektur yang mendukungnya.
Lihat asm dari gcc, clang, icc, dan MSVC pada explorer compiler Godbolt.
x86-64
gcc -O3 -std=gnu++11 -mpopcnt
memancarkan ini:PowerPC64
gcc -O3 -std=gnu++11
memancarkan (untukint
versi arg):Sumber ini sama sekali tidak spesifik x86 atau GNU, tetapi hanya mengkompilasi dengan baik untuk x86 dengan gcc / clang / icc.
Perhatikan juga bahwa fallback gcc untuk arsitektur tanpa popcount dengan instruksi tunggal adalah pencarian tabel byte per waktu. Ini tidak bagus untuk ARM, misalnya .
sumber
std::bitset::count
. setelah mengompilasi kompilasi ini ke satu__builtin_popcount
panggilan.Menurut pendapat saya, solusi "terbaik" adalah solusi yang dapat dibaca oleh programmer lain (atau programmer asli dua tahun kemudian) tanpa komentar berlebihan. Anda mungkin menginginkan solusi tercepat atau paling pintar yang beberapa telah disediakan tetapi saya lebih suka keterbacaan daripada kepintaran setiap saat.
Jika Anda ingin lebih cepat (dan dengan asumsi Anda mendokumentasikannya dengan baik untuk membantu penerus Anda), Anda bisa menggunakan pencarian tabel:
Meskipun ini bergantung pada ukuran tipe data tertentu sehingga mereka tidak portabel. Namun, karena banyak pengoptimalan kinerja yang tidak portabel, itu mungkin bukan masalah. Jika Anda ingin mudah dibawa, saya akan tetap menggunakan solusi yang mudah dibaca.
sumber
if ((value & 1) == 1) { count++; }
dengancount += value & 1
?Dari Hacker's Delight, hlm. 66, Gambar 5-2
Menjalankan instruksi ~ 20-ish (tergantung lengkungan), tanpa percabangan.
Kegembiraan Hacker sangat menyenangkan! Sangat dianjurkan.
sumber
Integer.bitCount(int)
menggunakan implementasi yang sama persis ini.pop
bukanpopulation_count
(ataupop_cnt
jika Anda harus memiliki abreviasi). @ MarscoBolis Saya menduga itu akan berlaku untuk semua versi Jawa, tetapi secara resmi itu akan tergantung pada implementasi :)Saya pikir cara tercepat — tanpa menggunakan tabel pencarian dan popcount — adalah sebagai berikut. Itu menghitung bit yang ditetapkan hanya dengan 12 operasi.
Ini bekerja karena Anda dapat menghitung jumlah total set bit dengan membaginya menjadi dua, menghitung jumlah bit set pada kedua bagian dan kemudian menambahkannya. Juga dikenal sebagai
Divide and Conquer
paradigma. Mari kita masuk ke detail ..Jumlah bit dalam dua bit dapat berupa
0b00
,0b01
atau0b10
. Mari kita coba selesaikan ini pada 2 bit ..Inilah yang diperlukan: kolom terakhir menunjukkan jumlah bit yang diset di setiap dua bit pasangan. Jika nomor bit kedua
>= 2 (0b10)
kemudianand
menghasilkan0b01
, yang lain menghasilkan0b00
.Pernyataan ini harus mudah dimengerti. Setelah operasi pertama kita memiliki jumlah bit yang diset dalam setiap dua bit, sekarang kita meringkas jumlah itu dalam setiap 4 bit.
Kami kemudian meringkas hasil di atas, memberi kami jumlah total bit yang ditetapkan dalam 4 bit. Pernyataan terakhir adalah yang paling sulit.
Mari kita jabarkan lebih lanjut ...
Ini mirip dengan pernyataan kedua; kami menghitung bit yang ditetapkan dalam kelompok 4 sebagai gantinya. Kita tahu — karena operasi kita sebelumnya — bahwa setiap gigitan memiliki jumlah bit yang ditetapkan di dalamnya. Mari kita lihat sebuah contoh. Misalkan kita memiliki byte
0b01000010
. Ini berarti gigitan pertama memiliki 4 bit yang ditetapkan dan yang kedua memiliki 2 bit yang ditetapkan. Sekarang kita tambahkan camilan itu bersama-sama.Ini memberi kita hitungan bit yang ditetapkan dalam byte, pada gigitan pertama
0b01100010
dan oleh karena itu kita menutupi empat byte terakhir dari semua byte dalam angka (membuangnya).Sekarang setiap byte memiliki hitungan set bit di dalamnya. Kita perlu menjumlahkan semuanya. Caranya adalah dengan melipatgandakan hasil
0b10101010
yang memiliki properti menarik. Jika nomor kami memiliki empat byteA B C D
, maka akan menghasilkan angka baru dengan byte iniA+B+C+D B+C+D C+D D
. Angka 4 byte dapat memiliki set maksimum 32 bit, yang dapat direpresentasikan sebagai0b00100000
.Yang kita butuhkan sekarang adalah byte pertama yang memiliki jumlah semua bit yang ditetapkan dalam semua byte, dan kita mendapatkannya
>> 24
. Algoritma ini dirancang untuk32 bit
kata - kata tetapi dapat dengan mudah dimodifikasi untuk64 bit
kata - kata.sumber
c =
? Sepertinya ini harus dihilangkan. Lebih lanjut, sarankan set paren tambahan A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" untuk menghindari beberapa peringatan klasik.popcount(int v)
danpopcount(unsigned v)
. Untuk portabilitas, pertimbangkanpopcount(uint32_t v)
, dll. Sangat suka bagian * 0x1010101.return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
jadi kita tidak perlu menghitung surat untuk melihat apa yang sebenarnya Anda lakukan (karena Anda membuang yang pertama0
, saya tidak sengaja berpikir Anda menggunakan pola bit yang salah (terbalik) sebagai topeng - itu sampai saya perhatikan hanya ada 7 huruf dan bukan 8).Saya bosan, dan menghitung satu miliar iterasi dari tiga pendekatan. Kompiler adalah gcc -O3. CPU adalah apa pun yang mereka masukkan ke dalam gen 1 Macbook Pro.
Yang tercepat adalah yang berikut, pada 3,7 detik:
Tempat kedua pergi ke kode yang sama tetapi mencari 4 byte bukannya 2 kata setengah. Itu membutuhkan waktu sekitar 5,5 detik.
Tempat ketiga pergi ke pendekatan 'samping samping' sedikit-twiddling, yang membutuhkan 8,6 detik.
Tempat keempat adalah __builtin_popcount () GCC, pada 11 detik yang memalukan.
Pendekatan menghitung sedikit demi sedikit lebih lambat, dan saya bosan menunggu sampai selesai.
Jadi, jika Anda peduli dengan kinerja di atas segalanya, maka gunakan pendekatan pertama. Jika Anda peduli, tetapi tidak cukup untuk menghabiskan 64Kb RAM di atasnya, gunakan pendekatan kedua. Kalau tidak, gunakan pendekatan satu-bit-pada-waktu-baca yang dapat dibaca (tapi lambat)
Sulit untuk memikirkan situasi di mana Anda ingin menggunakan pendekatan bit-twiddling.
Sunting: Hasil serupa di sini .
sumber
Jika Anda menggunakan Java, metode bawaan
Integer.bitCount
akan melakukannya.sumber
Biarkan saya jelaskan algoritma ini.
Algoritma ini didasarkan pada Divide and Conquer Algorithm. Misalkan ada bilangan bulat 8bit 213 (11010101 dalam biner), algoritmenya bekerja seperti ini (setiap kali menggabungkan dua blok tetangga):
sumber
Ini adalah salah satu pertanyaan di mana itu membantu untuk mengetahui arsitektur mikro Anda. Saya hanya menghitung waktu dua varian di bawah gcc 4.3.3 yang dikompilasi dengan -O3 menggunakan inline C ++ untuk menghilangkan overhead panggilan fungsi, satu miliar iterasi, menjaga jumlah berjalan dari semua jumlah untuk memastikan kompiler tidak menghapus sesuatu yang penting, menggunakan rdtsc untuk pengaturan waktu ( siklus clock tepat).
Hacker Delight yang tidak dimodifikasi membutuhkan waktu 12,2 gigacycles. Versi paralel saya (menghitung bit dua kali lebih banyak) berjalan dalam 13,0 gigacycles. Total 10,5 berlalu untuk keduanya secara bersamaan dengan Core Duo 2.4GHz. 25 gigacycles = lebih dari 10 detik pada frekuensi jam ini, jadi saya yakin timing saya tepat.
Ini ada hubungannya dengan rantai ketergantungan instruksi, yang sangat buruk untuk algoritma ini. Saya hampir bisa menggandakan kecepatan lagi dengan menggunakan sepasang register 64-bit. Bahkan, jika saya pintar dan menambahkan x + ya sedikit lebih cepat saya bisa mencukur beberapa shift. Versi 64-bit dengan beberapa tweak kecil akan keluar bahkan, tetapi menghitung bit dua kali lebih banyak lagi.
Dengan register 128 bit SIMD, satu lagi faktor dua, dan set instruksi SSE sering juga memiliki jalan pintas yang cerdas.
Tidak ada alasan untuk kode menjadi sangat transparan. Antarmuka sederhana, algoritme dapat direferensikan secara online di banyak tempat, dan dapat dilakukan uji unit yang komprehensif. Programmer yang menemukan itu bahkan mungkin belajar sesuatu. Operasi bit ini sangat alami pada level mesin.
OK, saya memutuskan untuk menggunakan versi 64-bit tweak. Untuk yang satu ini sizeof (unsigned long) == 8
Kelihatannya benar (saya tidak menguji dengan hati-hati). Sekarang waktunya keluar pada 10,70 gigacycles / 14,1 gigacycles. Angka itu kemudian menjumlahkan 128 miliar bit dan sesuai dengan 5.9 yang berlalu pada mesin ini. Versi non-paralel mempercepat sedikit karena saya menjalankan dalam mode 64-bit dan suka register 64-bit sedikit lebih baik daripada register 32-bit.
Mari kita lihat apakah ada sedikit lebih banyak OOO pipelining yang bisa didapat di sini. Ini sedikit lebih terlibat, jadi saya benar-benar diuji sedikit. Setiap istilah saja berjumlah 64, semua jumlah gabungan menjadi 256.
Saya senang sesaat, tetapi ternyata gcc memainkan trik inline dengan -O3 meskipun saya tidak menggunakan kata kunci inline dalam beberapa tes. Ketika saya membiarkan trik bermain gcc, satu miliar panggilan ke pop4 () membutuhkan 12,56 gigacycles, tapi saya memutuskan itu melipat argumen sebagai ekspresi konstan. Angka yang lebih realistis tampaknya 19.6gc untuk 30% percepatan lainnya. Loop pengujian saya sekarang terlihat seperti ini, memastikan setiap argumen cukup berbeda untuk menghentikan gcc dari memainkan trik.
256 miliar bit dijumlahkan dalam 8.17s telah berlalu. Berfungsi untuk 1,02 detik untuk 32 juta bit sebagaimana dibandingkan dalam tabel 16-bit. Tidak dapat membandingkan secara langsung, karena bangku lainnya tidak memberikan kecepatan jam, tetapi sepertinya saya telah menampar ingot dari edisi tabel 64KB, yang merupakan penggunaan tragis dari cache L1 di tempat pertama.
Pembaruan: memutuskan untuk melakukan yang jelas dan membuat pop6 () dengan menambahkan empat baris duplikat. Datang ke 22,8gc, 384 miliar bit dijumlahkan dalam 9,5 yang telah berlalu. Jadi ada 20% lagi Sekarang pada 800ms untuk 32 miliar bit.
sumber
Mengapa tidak dibagi secara iteratif dengan 2?
Saya setuju bahwa ini bukan yang tercepat, tetapi "terbaik" agak ambigu. Saya berpendapat bahwa "terbaik" harus memiliki unsur kejelasan
sumber
Twiddling Hacker's Delight menjadi jauh lebih jelas ketika Anda menulis pola bit.
Langkah pertama menambahkan bit genap ke bit aneh, menghasilkan jumlah bit di masing-masing bit. Langkah-langkah lain menambahkan potongan-potongan tingkat tinggi ke potongan-potongan tingkat rendah, menggandakan ukuran potongan sepanjang jalan, sampai kita memiliki hitungan akhir mengambil seluruh int.
sumber
Untuk media senang antara tabel pencarian 32 dan iterasi melalui setiap bit secara individual:
Dari http://ctips.pbwiki.com/CountBits
sumber
Ini bisa dilakukan di
O(k)
, di manak
jumlah bit diatur.sumber
n &= (n-1)
.Itu bukan solusi tercepat atau terbaik, tetapi saya menemukan pertanyaan yang sama di jalan saya, dan saya mulai berpikir dan berpikir. akhirnya saya menyadari bahwa itu dapat dilakukan seperti ini jika Anda mendapatkan masalah dari sisi matematika, dan menggambar grafik, maka Anda menemukan bahwa itu adalah fungsi yang memiliki beberapa bagian periodik, dan kemudian Anda menyadari perbedaan antara periode ... jadi ini dia:
sumber
def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
Fungsi yang Anda cari sering disebut "jumlah sideways" atau "jumlah populasi" dari angka biner. Knuth membahasnya dalam pra-Fascicle 1A, hal11-12 (walaupun ada referensi singkat dalam Volume 2, 4.6.3- (7).)
The lokus classicus adalah artikel Peter Wegner "Sebuah Teknik untuk Ones Menghitung dalam Binary Computer", dari Komunikasi ACM , Volume 3 (1960) Nomor 5, halaman 322 . Dia memberikan dua algoritma berbeda di sana, satu dioptimalkan untuk angka yang diharapkan "jarang" (yaitu, memiliki sejumlah kecil) dan satu untuk kasus sebaliknya.
sumber
sumber
Beberapa pertanyaan terbuka: -
kita dapat memodifikasi algo untuk mendukung angka negatif sebagai berikut: -
sekarang untuk mengatasi masalah kedua kita bisa menulis algo seperti: -
untuk referensi lengkap lihat:
http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html
sumber
Saya pikir metode Brian Kernighan akan berguna juga ... Itu melewati sebanyak iterasi karena ada bit yang ditetapkan. Jadi jika kita memiliki kata 32-bit dengan hanya set bit tinggi, maka itu hanya akan melewati loop.
sumber
Saya menggunakan kode di bawah ini yang lebih intuitif.
Logika: n & (n-1) me-reset bit set terakhir dari n.
PS: Saya tahu ini bukan O (1) solusi, walaupun itu solusi yang menarik.
sumber
O(ONE-BITS)
. Ini memang O (1) karena paling banyak ada 32 bit tunggal.Apa maksud Anda dengan "Algoritma terbaik"? Kode singkat atau kode cepat? Kode Anda terlihat sangat elegan dan memiliki waktu eksekusi yang konstan. Kode ini juga sangat pendek.
Tetapi jika kecepatan adalah faktor utama dan bukan ukuran kode maka saya pikir tindak lanjutnya bisa lebih cepat:
Saya pikir ini tidak akan lebih cepat untuk nilai 64 bit tetapi nilai 32 bit bisa lebih cepat.
sumber
Saya menulis makro bitcount cepat untuk mesin RISC di sekitar tahun 1990. Tidak menggunakan aritmatika lanjutan (perkalian, pembagian,%), pengambilan memori (terlalu lambat), cabang (terlalu lambat), tetapi ia menganggap CPU memiliki 32-bit barrel shifter (dengan kata lain, >> 1 dan >> 32 mengambil jumlah siklus yang sama.) Asumsinya adalah bahwa konstanta kecil (seperti 6, 12, 24) tidak memerlukan biaya apa pun untuk dimuat ke register, atau disimpan di temporaries dan digunakan kembali berulang-ulang.
Dengan asumsi ini, ia menghitung 32 bit dalam sekitar 16 siklus / instruksi pada kebanyakan mesin RISC. Perhatikan bahwa 15 instruksi / siklus dekat dengan batas bawah pada jumlah siklus atau instruksi, karena tampaknya mengambil setidaknya 3 instruksi (mask, shift, operator) untuk memotong jumlah penambahan menjadi setengah, jadi log_2 (32) = 5, 5 x 3 = 15 instruksi adalah quasi-lowerbound.
Inilah rahasia untuk langkah pertama dan paling rumit:
jadi jika saya mengambil kolom 1 (A) di atas, geser ke kanan 1 bit, dan kurangi dari AB, saya mendapatkan output (CD). Ekstensi ke 3 bit serupa; Anda dapat memeriksanya dengan tabel boolean 8 baris seperti milik saya di atas jika diinginkan.
sumber
jika Anda menggunakan C ++ opsi lain adalah menggunakan metaprogramming template:
penggunaan akan:
Anda tentu saja dapat memperluas templat ini untuk menggunakan berbagai jenis (bahkan ukuran bit pendeteksi otomatis) tapi saya tetap membuatnya mudah untuk kejelasan.
sunting: lupa menyebutkan ini bagus karena harus bekerja di kompiler C ++ dan pada dasarnya hanya membuka gulungan Anda untuk Anda jika nilai konstan digunakan untuk jumlah bit (dengan kata lain, saya cukup yakin itu adalah metode umum tercepat Anda akan menemukan)
sumber
constexpr
.Saya sangat menyukai contoh ini dari file keberuntungan:
Saya suka yang terbaik karena sangat cantik!
sumber
Java JDK1.5
Integer.bitCount (n);
di mana n adalah angka yang 1-nya harus dihitung.
periksa juga,
sumber
Saya menemukan implementasi penghitungan bit dalam array dengan menggunakan instruksi SIMD (SSSE3 dan AVX2). Ini memiliki kinerja 2-2,5 kali lebih baik daripada jika akan menggunakan fungsi intrinsik __popcnt64.
Versi SSSE3:
Versi AVX2:
sumber
Saya selalu menggunakan ini dalam Pemrograman Kompetitif dan mudah untuk menulis dan efisien:
sumber
Ada banyak algoritma untuk menghitung bit yang ditetapkan; tapi saya pikir yang terbaik adalah yang lebih cepat! Anda dapat melihat detailnya di halaman ini:
Bit Twiddling Hacks
Saya menyarankan yang ini:
Menghitung bit yang diatur dalam kata-kata 14, 24, atau 32-bit menggunakan instruksi 64-bit
Metode ini membutuhkan CPU 64-bit dengan divisi modulus cepat agar efisien. Opsi pertama hanya membutuhkan 3 operasi; opsi kedua membutuhkan 10; dan opsi ketiga memakan waktu 15.
sumber
Solusi C # cepat menggunakan tabel jumlah bit Byte yang dihitung sebelumnya dengan percabangan pada ukuran input.
sumber
(0xe994 >>(k*2))&3
, tanpa akses memori ...Berikut ini adalah modul portabel (ANSI-C) yang dapat membandingkan setiap algoritma Anda pada arsitektur apa pun.
CPU Anda memiliki 9 bit byte? Tidak masalah :-) Saat ini mengimplementasikan 2 algoritma, algoritma K&R dan tabel pencarian byte yang bijaksana. Tabel pencarian rata-rata 3 kali lebih cepat dari algoritma K&R. Jika seseorang dapat menemukan cara untuk membuat algoritma "Hacker's Delight" portabel jangan ragu untuk menambahkannya.
.
sumber
apa yang bisa kamu lakukan adalah
logika di balik ini adalah bit n-1 terbalik dari bit set paling kanan dari n. jika n = 6 yaitu 110 maka 5 adalah 101 bit dibalik dari bit set paling kanan dari n. jadi jika kita & dua ini kita akan membuat bit paling kanan 0 di setiap iterasi dan selalu pergi ke bit set paling kanan berikutnya. Oleh karena itu, menghitung bit yang ditetapkan. Kompleksitas waktu terburuk akan menjadi O (logn) ketika setiap bit diatur.
sumber