32 bitlik bir tam sayıdaki set bitlerinin sayısı nasıl hesaplanır?


868

7 sayısını temsil eden 8 bit şu şekildedir:

00000111

Üç bit ayarlanmıştır.

32 bit tam sayıdaki set bitlerinin sayısını belirlemek için algoritmalar nelerdir?


101
Bu Hamming ağırlığı BTW'dir.
Purfideas

11
Bunun için gerçek dünya uygulaması nedir? (Bu bir eleştiri olarak kabul edilmez - sadece merak ediyorum.)
jonmorgan

8
İletişimde basit hata tespiti olarak kullanılan eşlik bitinin hesaplanması (yukarıya bak).
Dialecticus

8
@Dialecticus, bir parite bitini hesaplamak Hamming ağırlığını hesaplamaktan daha ucuzdur
finnw

15
@spookyjon Diyelim ki biraz bitişik bir matris olarak temsil edilen bir grafiğiniz var. Bir tepe noktasının kenar sayısını hesaplamak istiyorsanız, bit kümesindeki bir satırın Hamming ağırlığını hesaplamak için kaynar.
fuz

Yanıtlar:


850

Bu, ' Hamming Weight ', ' Hamming Weight ', 'popcount' veya 'yandan toplama' olarak bilinir .

'En iyi' algoritma gerçekten hangi CPU'da olduğunuza ve kullanım düzeninizin ne olduğuna bağlıdır.

Bazı CPU'ların bunu yapmak için tek bir yerleşik talimatı vardır ve diğerlerinde bit vektörleri üzerinde çalışan paralel talimatlar vardır. Paralel talimatlar ( popcntdesteklendiği CPU'larda x86 gibi ) neredeyse kesinlikle en hızlı olacaktır. Diğer bazı mimariler, döngü başına biraz test eden mikrokodik bir döngü ile uygulanan yavaş bir talimata sahip olabilir ( alıntı gerekir ).

Önceden doldurulmuş bir tablo arama yöntemi, CPU'nuz büyük bir önbelleğe sahipse ve / veya bu talimatların çoğunu sıkı bir döngüde gerçekleştiriyorsanız çok hızlı olabilir. Ancak CPU'nun tablonun bir kısmını ana bellekten alması gereken bir 'önbellek kaçışının' maliyeti nedeniyle zarar görebilir. (Tabloyu küçük tutmak için her baytı ayrı ayrı arayın.)

Baytlarınızın çoğunlukla 0 veya çoğunlukla 1 olacağını biliyorsanız, bu senaryolar için çok etkili algoritmalar vardır.

Çok iyi bir genel amaçlı algoritmanın 'paralel' veya 'değişken hassasiyetli SWAR algoritması' olarak bilinen aşağıdaki olduğuna inanıyorum. Bunu C benzeri bir sahte dilde ifade ettim, belirli bir dil (örneğin C ++ için uint32_t ve Java'da >>> kullanarak) için ayarlamanız gerekebilir:

int numberOfSetBits(uint32_t i)
{
     // Java: use int, and use >>> instead of >>
     // C or C++: use uint32_t
     i = i - ((i >> 1) & 0x55555555);
     i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
     return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

JavaScript için: tamsayıya zorlamak ile |0performans için: İlk satırı değiştirini = (i|0) - ((i >> 1) & 0x55555555);

Bu, tartışılan herhangi bir algoritmanın en kötü durum davranışına sahiptir, bu nedenle attığınız herhangi bir kullanım şekli veya değeri ile etkili bir şekilde ilgilenir.


Bu SWAR bithack'i nasıl çalışır:

i = i - ((i >> 1) & 0x55555555);

İlk adım, tek / çift bitleri izole etmek, sıralamak için değiştirmek ve eklemek için maskelemenin optimize edilmiş bir versiyonudur. Bu, 2 bit akümülatörlerde 16 ayrı ekleme yapar ( SWAR = Bir Kayıt İçinde SIMD ). Gibi (i & 0x55555555) + ((i>>1) & 0x55555555).

Bir sonraki adım, bu 16x 2 bit akümülatörlerin tek / hatta sekizini alır ve tekrar ekleyerek 8x 4 bit toplamları üretir. i - ...Sadece değişen önce / sonra maske bu yüzden optimizasyon bu kez mümkün değildir. Kaydırmalarda 32 bit sabitleri ayrı ayrı oluşturması gereken ISA'lar için derleme yaparken aynı 0x33...sabiti iki kez kullanmak 0xccc...iyi bir şeydir.

Son kaydırma ve ekleme adımı (i + (i >> 4)) & 0x0F0F0F0F4x 8 bit akümülatörlere genişler. Bu maske daha sonra herhangi bir 4 bit toplayıcı maksimum değer olduğundan, daha önce ilave edilmesi yerine 4, karşılık gelen giriş uçları her 4 bit ayarlanmış olması halinde. 4 + 4 = 8 hala 4 bite sığar, bu yüzden kırıntı elemanları arasında taşımak imkansızdır i + (i >> 4).

Şimdiye kadar bu, birkaç akıllı optimizasyonla SWAR tekniklerini kullanan oldukça normal bir SIMD'dir. Aynı desenle 2 adım daha devam etmek, 2x 16-bit'e sonra 1x 32-bit sayılarına kadar genişleyebilir. Ancak hızlı donanıma sahip makinelerde daha verimli bir yol var:

Yeterli sayıda "elemana" sahip olduğumuzda, büyü sabiti ile çarpma tüm öğeleri üst elemana toplayabilir . Bu durumda bayt elemanları. Çarpma, sola kaydırma ve ekleme ile yapılır, böylece sonuçları çoğaltın . x * 0x01010101x + (x<<8) + (x<<16) + (x<<24) Bizim 8 bit elemanları yeterince geniş (ve yeterince küçük sayımları tutarak) bu eldeyi üretmek edilmemesidir içine o üst 8 bit.

Bunun 64 bit sürümü, 0x0101010101010101 çarpanıyla 64 bit tamsayıda 8x 8 bit öğe yapabilir ve yüksek baytı ile ayıklayabilir >>56. Yani fazladan bir adım atmıyor, sadece daha geniş sabitler. __builtin_popcountllDonanım popcnttalimatı etkinleştirilmediğinde GCC x86 sistemlerinde kullanır . Bunun için builtins veya intrinsics kullanabiliyorsanız, derleyiciye hedefe özgü optimizasyon yapma şansı verin.


Daha geniş vektörler için tam SIMD ile (örneğin tüm diziyi sayma)

Bu bitsel-SWAR algoritması, SIMD'li, ancak kullanılabilir popcount komutu olmayan CPU'larda bir hızlanma için, tek bir tamsayı yazmacı yerine, birden fazla vektör öğesinde aynı anda yapılabilecek şekilde paralelleştirilebilir. (örn. Nehalem veya sonraki sürümlerinde değil, herhangi bir CPU'da çalışması gereken x86-64 kodu.)

Bununla birlikte, popcount için vektör talimatlarını kullanmanın en iyi yolu, genellikle her bir baytın paralel olarak 4 bit için bir tablo araması yapmak için değişken bir shuffle kullanmaktır. (4 bit, bir vektör yazmacında tutulan 16 giriş tablosunu indeksler).

Intel CPU'lar üzerinde bir performans gösterebilirler donanım 64bit popcnt talimat SSSE3 PSHUFBuygulanmasını paralel bit 2 faktörüyle ilgili tarafından, ama sadece sizin derleyici sadece sağ alırsa . Aksi takdirde SSE önemli ölçüde öne çıkabilir. Daha yeni derleyici sürümleri, Intel'deki popcnt yanlış bağımlılık sorununun farkındadır .

Referanslar:


87
Ha! NumberOfSetBits () işlevini seviyorum, ancak bir kod inceleme yoluyla elde etmek iyi şanslar. :-)
Jason S

37
Belki de unsigned int, herhangi bir işaret biti komplikasyonundan arınmış olduğunu kolayca göstermek için kullanmalıdır . Ayrıca uint32_t, tüm platformlarda beklediğiniz gibi, daha güvenli olur mu?
Craig McQueen

35
@nonnb: Aslında, yazıldığı gibi kod buggy ve bakıma ihtiyacı var. >>negatif değerler için uygulama tanımlıdır. Argümanın değiştirilmesi (veya yayınlanması) gerekir unsignedve kod 32 bit'e özgü olduğundan, muhtemelen kullanıyor olmalıdır uint32_t.
R .. GitHub BUZA YARDIMCI DURDUR

6
Gerçekten sihir değil. Bit kümeleri ekliyor, ancak bunu bazı akıllı optimizasyonlarla yapıyor. Cevapta verilen wikipedia bağlantısı neler olduğunu açıklamak için iyi bir iş çıkarıyor ama satır satır gideceğim. 1) Her bit çiftindeki bit sayısını sayın, bu sayımı bu bit çiftine koyun (00, 01 veya 10'a sahip olacaksınız); buradaki "akıllı" bit, bir maskeden kaçınan çıkartmadır. 2) Bu toplam bit çiftlerinin çiftlerini karşılık gelen kemirgenlerine ekleyin; Burada zekice bir şey yok ama her kırıntıda 0-4 değeri olacak. (devamı)
dash-tom-bang

8
Başka bir not, sabitleri uygun şekilde genişleterek 64 ve 128 bit kayıtlara kadar uzanır. İlginç bir şekilde (bana göre), bu sabitler de ~ 0/3, 5, 17 ve 255'tir; ilk üçü 2 ^ n + 1'dir. Bu daha ona bakmak ve duş hakkında düşünmek daha mantıklı. :)
dash-tom-bang

214

Ayrıca derleyicilerinizin yerleşik işlevlerini de göz önünde bulundurun.

Örneğin GNU derleyicisinde şunları kullanabilirsiniz:

int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);

En kötü durumda derleyici bir işleve çağrı oluşturur. En iyi durumda derleyici aynı işi daha hızlı yapmak için bir cpu talimatı yayar.

GCC intrinsikleri birden fazla platformda bile çalışır. Popcount, x86 mimarisinde ana akım olacak, bu yüzden şimdi içsel kullanmaya başlamak mantıklı. Diğer mimariler yıllardır popcount'a sahiptir.


X86'da, derleyiciye , aynı nesilde eklenen vektör komutlarıyla popcntkomut için -mpopcntveya -msse4.2aynı zamanda etkinleştirmek için destek alabileceğini söyleyebilirsiniz . GCC x86 seçeneklerine bakın . -march=nehalem(veya -march=kodunuzun varsaymasını ve ayarlamasını istediğiniz CPU ne olursa olsun) iyi bir seçim olabilir. Ortaya çıkan ikili dosyayı daha eski bir CPU'da çalıştırmak geçersiz talimat hatasına neden olur.

-march=native İkilileri, üzerine kurduğunuz makine için optimize etmek için (gcc, clang veya ICC ile) kullanın.

MSVC, x86 popcntkomutu için gerçek bir özellik sağlar , ancak gcc'den farklı olarak, gerçekten de donanım eğitimi için gerçek bir özelliktir ve donanım desteği gerektirir.


Yerleşik std::bitset<>::count()yerine kullanma

Teorik olarak, hedef CPU için verimli bir şekilde nasıl hesaplanacağını bilen herhangi bir derleyici, bu işlevselliği ISO C ++ ile ortaya çıkarmalıdır std::bitset<>. Pratikte, bazı durumlarda bazı hedef CPU'lar için bit-hack AND / shift / ADD ile daha iyi olabilirsiniz.

Donanım popcount'unun isteğe bağlı bir uzantı (x86 gibi) olduğu hedef mimariler için, tüm derleyiciler std::bitsetmevcut olduğunda bundan yararlanan bir mimariye sahip değildir . Örneğin, msvc etkinleştirmek için bir yol vardır popcntderleme zamanında destek ve her zaman kullanan bir tablo arama olsa bile, /Ox /arch:AVX(teknik için ayrı bir özelliği biraz olsa, SSE4.2 ima popcnt).

Ancak en azından her yerde çalışan taşınabilir bir şey elde edersiniz ve doğru hedef seçeneklerine sahip gcc / clang ile, onu destekleyen mimariler için donanım popcount'u alırsınız.

#include <bitset>
#include <limits>
#include <type_traits>

template<typename T>
//static inline  // static if you want to compile with -mpopcnt in one compilation unit but not others
typename std::enable_if<std::is_integral<T>::value,  unsigned >::type 
popcount(T x)
{
    static_assert(std::numeric_limits<T>::radix == 2, "non-binary type");

    // sizeof(x)*CHAR_BIT
    constexpr int bitwidth = std::numeric_limits<T>::digits + std::numeric_limits<T>::is_signed;
    // std::bitset constructor was only unsigned long before C++11.  Beware if porting to C++03
    static_assert(bitwidth <= std::numeric_limits<unsigned long long>::digits, "arg too wide for std::bitset() constructor");

    typedef typename std::make_unsigned<T>::type UT;        // probably not needed, bitset width chops after sign-extension

    std::bitset<bitwidth> bs( static_cast<UT>(x) );
    return bs.count();
}

Bkz gcc, clang, icc ve MSVC gelen asm Godbolt derleyici kaşif üzerinde.

x86-64 bunu gcc -O3 -std=gnu++11 -mpopcntyayar:

unsigned test_short(short a) { return popcount(a); }
    movzx   eax, di      # note zero-extension, not sign-extension
    popcnt  rax, rax
    ret
unsigned test_int(int a) { return popcount(a); }
    mov     eax, edi
    popcnt  rax, rax
    ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
    xor     eax, eax     # gcc avoids false dependencies for Intel CPUs
    popcnt  rax, rdi
    ret

PowerPC64 gcc -O3 -std=gnu++11yayıyor ( intarg sürümü için):

    rldicl 3,3,0,32     # zero-extend from 32 to 64-bit
    popcntd 3,3         # popcount
    blr

Bu kaynak hiç x86'ya veya GNU'ya özgü değildir, ancak yalnızca gcc / clang / icc içeren x86 için iyi derler.

Ayrıca, gcc'nin tek komutlu popcount içermeyen mimariler için geri dönüşünün bir kerede bir bayt tablo araması olduğunu unutmayın. Örneğin, ARM için bu harika değil .


5
Bunun genel olarak iyi bir uygulama olduğunu kabul ediyorum, ancak XCode / OSX / Intel'de burada yayınlanan önerilerin çoğundan daha yavaş kod ürettiğini gördüm. Ayrıntılar için cevabıma bakın.

5
Intel i5 / i7, genel amaçlı kayıtlar kullanarak bunu yapan SSE4 POPCNT talimatına sahiptir. Sistemimde GCC bu intrinsic kullanarak bu talimatı yaymaz, sanırım henüz -march = nehalem seçeneği nedeniyle.
matja

3
@matja, -msse4.2 ile derlersem GCC 4.4.1im popcnt komutunu yayar
Nils Pipenbrinck

74
c ++ 'ları kullanın std::bitset::count. bu satır aradan sonra tek bir __builtin_popcountçağrıyı derler .
deft_code

1
@nlucaroni Şey, evet. Zaman değişiyor. Bu cevabı 2008'de yazdım. Günümüzde yerli popcount'umuz var ve platform buna izin veriyorsa, içsel tek bir montajcı bildirisine derleyecektir.
Nils Pipenbrinck

184

Benim düşünceme göre, "en iyi" çözüm, bolca yorum yapmadan başka bir programcı (veya iki yıl sonra orijinal programcı) tarafından okunabilen çözümdür. Bazılarının zaten sağladığı en hızlı veya en akıllı çözümü isteyebilirsiniz, ancak her zaman akıllılığa göre okunabilirliği tercih ederim.

unsigned int bitCount (unsigned int value) {
    unsigned int count = 0;
    while (value > 0) {           // until all bits are zero
        if ((value & 1) == 1)     // check lower bit
            count++;
        value >>= 1;              // shift bits, removing lower bit
    }
    return count;
}

Daha fazla hız istiyorsanız (ve haleflerinize yardımcı olmak için bunu iyi bir şekilde belgelediğinizi varsayarsanız) bir tablo araması kullanabilirsiniz:

// Lookup table for fast calculation of bits set in 8-bit unsigned char.

static unsigned char oneBitsInUChar[] = {
//  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F (<- n)
//  =====================================================
    0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, // 0n
    1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, // 1n
    : : :
    4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8, // Fn
};

// Function for fast calculation of bits set in 16-bit unsigned short.

unsigned char oneBitsInUShort (unsigned short x) {
    return oneBitsInUChar [x >>    8]
         + oneBitsInUChar [x &  0xff];
}

// Function for fast calculation of bits set in 32-bit unsigned int.

unsigned char oneBitsInUInt (unsigned int x) {
    return oneBitsInUShort (x >>     16)
         + oneBitsInUShort (x &  0xffff);
}

Her ne kadar bunlar belirli veri türü boyutlarına bağlı olsa da o kadar taşınabilir değillerdir. Ancak, birçok performans optimizasyonu zaten taşınabilir olmadığından, bu bir sorun olmayabilir. Taşınabilirlik istiyorsanız, okunabilir çözüme sadık kalırdım.


21
2'ye bölmek ve "kaydırma bitleri ..." olarak yorumlamak yerine, sadece shift operatörünü (>>) kullanmalı ve yorumu bırakmalısınız.
indiv

9
o yerine daha mantıklı olmaz if ((value & 1) == 1) { count++; }ile count += value & 1?
Nisan'da Ponkadoodle

21
Hayır, en iyi çözüm bu durumda en okunabilir çözüm değil. Burada en iyi algoritma en hızlı algoritmadır.
NikiC

21
Bu tamamen senin fikrin. "En iyi" nin nasıl ölçüleceğine dair soruya değinilmedi, "performans" veya "hızlı" kelimeleri hiçbir yerde görülemez. Bu yüzden okunabilir olmayı seçtim.
paxdiablo

3
Bu cevabı 3 yıl sonra okuyorum ve en iyi cevap olarak görüyorum çünkü okunabilir ve daha fazla yorumu var. dönem.
waka-waka-waka

98

Hacker'ın Lokumundan, s. 66, Şekil 5-2

int pop(unsigned x)
{
    x = x - ((x >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    x = x + (x >> 16);
    return x & 0x0000003F;
}

~ 20-ish talimatları (ark bağımlı), dallanma olmadan yürütür.

Hacker'ın Delight olduğunu enfes! Şiddetle tavsiye edilir.


8
Java yöntemi Integer.bitCount(int)aynı tam uygulamayı kullanır.
Marco Bolis

Bunu takiben biraz sorun yaşıyorum - 32 bit yerine yalnızca 16 bit değerlere baktığımızda nasıl değişebilir?
Jeremy Blum

Belki hackerlar zevk hoş, ama ben popyerine population_count(ya pop_cntda bir kısaltma olması gerekir) bunu arayan herkese iyi bir tekme vermek istiyorum. @MarcoBolis Java'nın tüm sürümleri için geçerli olacağını, ancak resmen uygulamaya bağlı olacağını düşünüyorum :)
Maarten Bodewes

Ve bu, kabul edilen cevaptaki kod gibi çarpma gerektirmez.
Alex

64 bit'e genelleme yapıldığında bir sorun olduğunu unutmayın. Maske nedeniyle sonuç 64 olamaz.
Albert van der Horst

76

Arama tablolarını ve popcount'u kullanmadan en hızlı yolun şu olduğunu düşünüyorum . Sadece 12 işlemle set bitlerini sayar.

int popcount(int v) {
    v = v - ((v >> 1) & 0x55555555);                // put count of each 2 bits into those 2 bits
    v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // put count of each 4 bits into those 4 bits  
    return c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
}

Çalışır, çünkü iki yarıya bölünerek, her iki yarıdaki set bitlerinin sayısını sayarak ve daha sonra toplayarak toplam set bitlerini sayabilirsiniz. Divide and ConquerParadigma olarak da bilin . Ayrıntılara girelim ..

v = v - ((v >> 1) & 0x55555555); 

İki bit bit sayısı olabilir 0b00, 0b01ya da 0b10. Bunu 2 bit üzerinde çözmeye çalışalım ..

 ---------------------------------------------
 |   v    |   (v >> 1) & 0b0101   |  v - x   |
 ---------------------------------------------
   0b00           0b00               0b00   
   0b01           0b00               0b01     
   0b10           0b01               0b01
   0b11           0b01               0b10

Gerekli olan buydu: son sütun her iki bit çiftindeki ayarlanmış bit sayısını gösterir. İki bitlik sayı ise >= 2 (0b10)o zaman andüretir 0b01, başka ürettiği 0b00.

v = (v & 0x33333333) + ((v >> 2) & 0x33333333); 

Bu ifadenin anlaşılması kolay olmalıdır. İlk işlemden sonra her iki bitte ayarlanmış bit sayısı vardır, şimdi bu sayımı her 4 bitte özetliyoruz.

v & 0b00110011         //masks out even two bits
(v >> 2) & 0b00110011  // masks out odd two bits

Daha sonra yukarıdaki sonucu toplayarak bize 4 bitlik toplam set biti sayısını veririz. Son ifade en zor olanıdır.

c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;

Hadi biraz daha yıkalım ...

v + (v >> 4)

İkinci ifadeye benzer; set bitlerini 4'lü gruplar halinde sayıyoruz. Önceki operasyonlarımız nedeniyle her nibble'ın içinde ayarlanmış bit sayısı olduğunu biliyoruz. Bir örnek verelim. Diyelim ki baytımız var 0b01000010. Bu, ilk kemirgenin 4 biti ve ikincisinin 2 biti olduğu anlamına gelir. Şimdi bu kemikleri birlikte ekliyoruz.

0b01000010 + 0b01000000

Bize ilk bayttaki bir bayttaki set bitlerinin sayısını verir 0b01100010 ve bu nedenle sayıdaki tüm baytların son dört baytını maskeleriz (onları atarız).

0b01100010 & 0xF0 = 0b01100000

Şimdi her baytın içinde ayarlanmış bit sayısı vardır. Hepsini bir araya getirmemiz gerekiyor. İşin püf noktası sonucu0b10101010 ilginç bir özelliğe sahip . Sayımızın dört baytı varsa, A B C Dbu baytlarla yeni bir sayıya neden olur A+B+C+D B+C+D C+D D. 4 baytlık bir sayı en fazla 32 bit olarak ayarlanabilir ve bu sayı olarak temsil edilebilir 0b00100000.

Şimdi ihtiyacımız olan tek şey, tüm baytlardaki tüm set bitlerinin toplamına sahip olan ilk bayttır ve bunu elde ederiz >> 24. Bu algoritma 32 bitkelimeler için tasarlanmıştır ancak kelimeler için kolayca değiştirilebilir 64 bit.


Ne c = hakkında? Görünüşe göre ortadan kaldırılmalıdır. Ayrıca, bazı klasik uyarıları önlemek için ekstra bir A "(((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" paren seti önerin.
chux - Monica'yı eski durumuna getir

4
Önemli bir özellik, bu 32 bit rutinin hem popcount(int v)ve hem de için çalışmasıdır popcount(unsigned v). Taşınabilirlik için düşünün popcount(uint32_t v), vb. Gerçekten * 0x1010101 bölümü gibi.
chux - Monica'yı eski durumuna döndür

Sos ? (kitap, bağlantı, yatırımcıların adları vb.) ÇOK memnuniyetle karşılanacaktır. Çünkü o zaman kod tabanlarımıza nereden geldiğine dair bir yorum ekleyerek yapıştırabiliriz.
v.oddou

1
Daha iyi netlik için son satırın şöyle yazılması gerektiğini düşünüyorum: return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;bu yüzden aslında ne yaptığınızı görmek için harfleri saymamız gerekmez (ilkini attığınızdan, 0yanlışlıkla yanlış (ters çevrilmiş) bit desenini maske olarak kullandığınızı düşündüm - yani 8 değil sadece 7 harf olduğunu fark edene kadar).
emem

Bu çarpma 0x01010101 işlemcinin bağlı olarak yavaş olabilir. Örneğin, eski PowerBook G4'ümde, 1 çarpma yaklaşık 4 ekleme kadar yavaştı (bölme kadar kötü değil, 1 bölme 23 ekleme kadar yavaştı).
George Koehler

54

Sıkıldım ve üç yaklaşımın milyarlarca yinelemesini zamanladım. Derleyici gcc -O3. CPU, 1. nesil Macbook Pro'ya koydukları şeydir.

3.7 saniyede en hızlısı şudur:

static unsigned char wordbits[65536] = { bitcounts of ints between 0 and 65535 };
static int popcount( unsigned int i )
{
    return( wordbits[i&0xFFFF] + wordbits[i>>16] );
}

İkincisi aynı koda gider ancak 2 yarım kelime yerine 4 bayt arar. Bu yaklaşık 5.5 saniye sürdü.

Üçüncülük, 8,6 saniye süren bit-twiddling 'yandan toplama' yaklaşımına gidiyor.

Dördüncülük, 11 saniye içinde GCC'nin __builtin_popcount () yöntemine gider.

Sayım birer birer yaklaşım daha yavaştı ve tamamlanmasını beklemekten sıkıldım.

Performansı her şeyden önce önemsiyorsanız, ilk yaklaşımı kullanın. Eğer ilgileniyorsanız, ancak 64Kb RAM harcamak için yeterli değilse, ikinci yaklaşımı kullanın. Aksi takdirde, her seferinde okunabilir (ancak yavaş) bir bit yaklaşımı kullanın.

Bit çevirme yaklaşımını kullanmak istediğiniz bir durumu düşünmek zor.

Düzenleme: Burada benzer sonuçlar .


49
@Mike, tablo önbellekte ise tablo tabanlı yaklaşım rakipsizdir. Bu mikro ölçütlerde gerçekleşir (örneğin sıkı bir döngüde milyonlarca test yapın). Ancak, bir önbellek özledim yaklaşık 200 döngü sürer ve en naif popcount bile burada daha hızlı olacaktır. Her zaman uygulamaya bağlıdır.
Nils Pipenbrinck

10
Bu rutini sıkı bir döngüde birkaç milyon kez çağırmıyorsanız, o zaman onun performansını önemsemek için hiçbir nedeniniz yoktur ve performans kaybı ihmal edilebilir olacağından naif ama okunabilir yaklaşımı da kullanabilirsiniz. Ve FWIW, 8 bit LUT 10-20 çağrıda önbellek ısınıyor.

6
Bunun, uygulamanızda - aslında ağır kaldırmayı yapan yöntemden yapılan bir yaprak çağrısı olduğu bir durumu hayal etmenin o kadar zor olduğunu düşünmüyorum. Başka neler olup bittiğine (ve iş parçacığı) bağlı olarak daha küçük sürüm kazanabilir. Referans konumunun daha iyi olması nedeniyle akranlarını yenen birçok algoritma yazılmıştır. Neden olmasın?
Jason

Bunu clang ile deneyin, yerleşik uygulamaları uygulamak çok daha akıllı.
Matt Joiner

3
GCC, -msse4.2 ile çağrılmadığı sürece popcont talimatı vermeyecektir, bu durum 'yana doğru ekleme'den daha hızlıdır.
lvella

54

Java kullanıyorsanız, yerleşik yöntem Integer.bitCountbunu yapar.


Sun farklı API'ler sağladığında, arka planda bir mantık kullanıyor olmalı, değil mi?
Vallabh Patade

2
Bir yan not olarak, Java'nın uygulama kullanan aynı tarafından işaret algoritması Kevin Little'ın .
Marco Bolis

2
Uygulama bir yana, bu muhtemelen sizden sonra (veya 6 ay sonra tekrar döndüğünüzde) kodunuzu koruyan geliştiriciler için en açık niyet
mesajıdır

31
unsigned int count_bit(unsigned int x)
{
  x = (x & 0x55555555) + ((x >> 1) & 0x55555555);
  x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
  x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);
  x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);
  x = (x & 0x0000FFFF) + ((x >> 16)& 0x0000FFFF);
  return x;
}

Bu algoritmayı açıklayayım.

Bu algoritma Bölme ve Conquer Algoritmasına dayanır. Bir 8 bit tam sayı 213 (ikili dosyada 11010101) olduğunu varsayalım, algoritma şu şekilde çalışır (her seferinde iki komşu bloğu birleştirir):

+-------------------------------+
| 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 |  <- x
|  1 0  |  0 1  |  0 1  |  0 1  |  <- first time merge
|    0 0 1 1    |    0 0 1 0    |  <- second time merge
|        0 0 0 0 0 1 0 1        |  <- third time ( answer = 00000101 = 5)
+-------------------------------+

7
Bu algoritma, okunamayacağı gerçeğine göre optimize edilmeden önce Matt Howells tarafından yayınlanan sürümdür.
Lefteris E

29

Bu, mikro mimarinizi bilmeye yardımcı olduğu sorulardan biridir. Ben sadece gcc 4.3.3 altında iki değişken zamanlama fonksiyon çağrısı yükü, bir milyar yinelemeleri ortadan kaldırmak için C ++ satırları kullanarak derlenmiş, zamanlayıcı için rdtsc kullanarak, önemli bir şey kaldırmak için tüm sayımların çalışma tutarını tutarak ( saat çevrimi hassas).

satır içi int pop2 (işaretsiz x, işaretsiz y)
{
    x = x - ((x >> 1) & 0x55555555);
    y = y - ((y >> 1) & 0x55555555);
    x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
    y = (y & 0x33333333) + ((y >> 2) & 0x33333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F;
    x = x + (x >> 8);
    y = y + (y >> 8);
    x = x + (x >> 16);
    y = y + (y >> 16);
    dönüş (x + y) & 0x000000FF;
}

Değiştirilmemiş Hacker'ın Lokumu 12.2 gigacycle aldı. Paralel versiyonum (iki bit kadar sayma) 13.0 gigacycles ile çalışıyor. 2.4GHz Core Duo'da her ikisi için toplam 10.5s geçti. 25 gigacycles = bu saat frekansında 10 saniyenin biraz üzerinde, bu yüzden zamanlamalarımın doğru olduğundan eminim.

Bu, bu algoritma için çok kötü olan talimat bağımlılık zincirleriyle ilgilidir. Bir çift 64 bit kayıt kullanarak hızı neredeyse iki katına çıkarabilirdim. Aslında, eğer zeki olsaydım ve x + ya biraz daha eklersem, bazı vardiyaları tıraş edebilirdim. Bazı küçük ayarlamalar ile 64-bit sürümü eşit çıkacaktı, ancak tekrar iki kat daha fazla sayılacak.

128 bit SIMD kayıtları ile, yine bir başka faktör ve SSE komut setleri de genellikle akıllı kısa yollara sahiptir.

Kodun özellikle şeffaf olması için hiçbir neden yoktur. Arayüz basittir, algoritmaya birçok yerde çevrimiçi olarak başvurulabilir ve kapsamlı birim testine uygundur. Tökezleyen programcı bir şeyler bile öğrenebilir. Bu bit işlemleri makine düzeyinde son derece doğaldır.

Tamam, tweaked 64-bit versiyonunu karşılaştırmaya karar verdim. Bunun için sizeof (unsigned long) == 8

satır içi int pop2 (işaretsiz uzun x, işaretsiz uzun y)
{
    x = x - ((x >> 1) & 0x5555555555555555);
    y = y - ((y >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    dönüş x & 0xFF;
}

Bu doğru görünüyor (Yine de dikkatle test etmiyorum). Şimdi zamanlamalar 10.70 gigacycles / 14.1 gigacycles olarak çıkıyor. Daha sonraki sayı 128 milyar biti topladı ve bu makinede geçen 5.9'lara karşılık geldi. 64-bit modunda çalıştığım ve 64-bit kayıtları 32-bit kayıtlardan biraz daha iyi olduğu için paralel olmayan versiyon biraz hızlanıyor.

Bakalım burada biraz daha fazla OOO boru hattı var mı. Bu biraz daha ilgiliydi, bu yüzden aslında biraz test ettim. Her terim tek başına 64'tür, hepsi toplam 256'dır.

satır içi int pop4 (işaretsiz uzun x, işaretsiz uzun y, 
                imzasız uzun u, imzasız uzun v)
{
  enum {m1 = 0x5555555555555555, 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF};

    x = x - ((x >> 1) & m1);
    y = y - ((y >> 1) & m1);
    u = u - ((u >> 1) & m1);
    v = v - ((v >> 1) & m1);
    x = (x & m2) + ((x >> 2) & m2);
    y = (y & m2) + ((y >> 2) & m2);
    u = (u & m2) + ((u >> 2) & m2);
    v = (v & m2) + ((v >> 2) & m2);
    x = x + y; 
    u = u + v; 
    x = (x & m3) + ((x >> 4) & m3);
    u = (u & m3) + ((u >> 4) & m3);
    x = x + u; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x & m4; 
    x = x + (x >> 32);
    dönüş x & 0x000001FF;
}

Bir an için heyecanlandım, ancak bazı testlerde inline anahtar kelimesini kullanmama rağmen gcc -O3 ile satır içi hileler oynuyor. Gcc'nin hile yapmasına izin verdiğimde, bir milyar pop4 () çağrısı 12.56 gigacycles alır, ancak bunun sabit ifadeler olarak argümanları katladığını belirledim. Daha gerçekçi bir sayı,% 30'luk bir hız artışı için 19.6gc gibi görünüyor. Test döngüm şimdi şuna benziyor, her bir argümanın gcc'nin hile yapmasını engelleyecek kadar farklı olduğundan emin olun.

   hitime b4 = rdtsc (); 
   için (işaretsiz uzun i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) 
      sum + = pop4 (i, i ^ 1, ~ i, i | 1); 
   hitime e4 = rdtsc (); 

8.17'lerde toplanan 256 milyar bit geçti. 16-bit tablo aramasında kıyaslandığı gibi 32 milyon bit için 1.02s'ye kadar çalışır. Doğrudan karşılaştırılamıyor, çünkü diğer tezgah saat hızı vermiyor, ancak LK önbelleğinin trajik bir kullanımı olan 64KB tablo sürümünün sümüğünü tokatladım gibi görünüyor.

Güncelleme: dört tane daha yinelenen satır ekleyerek bariz yapmaya ve pop6 () oluşturmaya karar verdi. 22.8 gc, 384 milyar bit toplam 9.5s toplandı geçti. 32 milyar bit için 800ms'de% 20 daha var.



28

Neden yinelemeli olarak 2'ye bölmüyorsunuz?

sayım = 0
n> 0 iken
  eğer (n% 2) == 1
    sayım + = 1
  n / = 2  

Bunun en hızlı olmadığını, ancak "en iyi" nin biraz belirsiz olduğunu kabul ediyorum. "En iyisinin" bir açıklık unsuru olması gerektiğini savunuyorum


Bu işe yarayacak ve anlaşılması kolay, ancak daha hızlı yöntemler var.
Matt Howells

2
Bunu ÇOK yapmazsanız , performans etkisi göz ardı edilebilir. Yani her şeyin eşit olması, Daniel'e 'en iyi' imaların "anlamsızca okunmadığını" kabul ediyorum.

2
Çeşitli yöntemler elde etmek için kasıtlı olarak 'en iyi'yi tanımlamamıştım. Bu tür bir bit-twiddling seviyesine düştüğümüzde yüzleşelim, muhtemelen bir şempanzenin yazdığı gibi uber-hızlı bir şey arıyoruz.
Matt Howells

6
Hatalı kod. Bir derleyici bundan iyi sonuç verebilir, ancak testlerimde GCC yapmadı. (N% 2) yerine (n & 1); VE MODULO'dan çok daha hızlı olmak. (N / = 2) yerine (n >> = 1); bitshif bölme çok daha hızlı.
Mecki

6
@Mecki: Benim testlerde, (, -O3 4.0) gcc yaptığı bariz optimizasyon yapmak.

26

Hacker'ın Delight bit döndürmesi, bit kalıplarını yazdığınızda çok daha net hale gelir.

unsigned int bitCount(unsigned int x)
{
  x = ((x >> 1) & 0b01010101010101010101010101010101)
     + (x       & 0b01010101010101010101010101010101);
  x = ((x >> 2) & 0b00110011001100110011001100110011)
     + (x       & 0b00110011001100110011001100110011); 
  x = ((x >> 4) & 0b00001111000011110000111100001111)
     + (x       & 0b00001111000011110000111100001111); 
  x = ((x >> 8) & 0b00000000111111110000000011111111)
     + (x       & 0b00000000111111110000000011111111); 
  x = ((x >> 16)& 0b00000000000000001111111111111111)
     + (x       & 0b00000000000000001111111111111111); 
  return x;
}

İlk adım, tek bitlere çift bitleri ekleyerek her ikisinde bir miktar bit üretir. Diğer adımlar, tüm int'i alan son sayım elde edene kadar, düşük sıralı parçalara yüksek sıralı parçalar ekler.


3
Bu çözüm, operatör önceliği ile ilgili küçük bir soruna sahip gibi görünmektedir. Her terim için şunu söylemelidir: x = ((((x >> 1) & 0b01010101010101010101010101010101) + (x & 0b01010101010101010101010101010101)); (yani ekstra parens eklendi).
Nopik

21

2 32 arama tablosu arasında ve her bir bit boyunca ayrı ayrı yineleme yapan mutlu bir ortam için :

int bitcount(unsigned int num){
    int count = 0;
    static int nibblebits[] =
        {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
    for(; num != 0; num >>= 4)
        count += nibblebits[num & 0x0f];
    return count;
}

Gönderen http://ctips.pbwiki.com/CountBits


Taşınabilir değil. CPU'nun 9 bit baytı varsa ne olur? Evet, orada gerçek CPU'lar var ...
Robert S. Barnes

15
Robert S. Barnes, bu işlev hala çalışacak. Yerel kelime boyutu hakkında hiçbir varsayımda bulunmaz ve "bayt" a hiç başvurmaz.
finnw

19

Bu, ayarlanan bit sayısının O(k)olduğu yerde yapılabilir k.

int NumberOfSetBits(int n)
{
    int count = 0;

    while (n){
        ++ count;
        n = (n - 1) & n;
    }

    return count;
}

Bu aslında Brian Kernighan'ın (onu hatırlıyor musun?) Algoritmasıdır ve daha az özlü n &= (n-1)formu kullandığı küçük değişiklikle .
Adrian Mole

17

Bu en hızlı veya en iyi çözüm değil, ama aynı soruyu kendi yolumda buldum ve düşünmeye ve düşünmeye başladım. Sonunda, problemi matematik tarafından alıp bir grafik çizerseniz, bunun böyle yapılabileceğini fark ettim, o zaman bunun periyodik bir kısmı olan bir fonksiyon olduğunu görüyorsunuz ve sonra dönemler arasındaki farkı fark ediyorsunuz ... Hadi bakalım:

unsigned int f(unsigned int x)
{
    switch (x) {
        case 0:
            return 0;
        case 1:
            return 1;
        case 2:
            return 1;
        case 3:
            return 2;
        default:
            return f(x/4) + f(x%4);
    }
}

4
oh bunu beğendim. nasıl piton sürümü dersin: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))()
underrun

10

Aradığınız işleve genellikle ikili sayının "yan toplamı" veya "nüfus sayısı" denir. Knuth bunu Fasikül 1A, ss11-12'de tartışıyor (Cilt 2, 4.6.3- (7) 'de kısa bir referans olmasına rağmen)

Coğrafyası Peter Wegner'in makale "Bir İkili Bilgisayar Sayma Ones için bir Tekniği" dir ACM Communications , Cilt 3 (1960) Sayı 5, sayfa 322 . Orada iki farklı algoritma veriyor, bunlardan biri "seyrek" olması beklenen sayılar için optimize edilmiş (yani az sayıda algoritmaya sahip) ve diğeri ise tersi durumda.


10
  private int get_bits_set(int v)
    {
      int c; // c accumulates the total bits set in v
        for (c = 0; v>0; c++)
        {
            v &= v - 1; // clear the least significant bit set
        }
        return c;
    }

9

Birkaç açık soru: -

  1. Sayı negatifse?
  2. Sayı 1024 ise, "yinelemeli olarak 2'ye böl" yöntemi 10 kez yinelenir.

aşağıdaki gibi negatif sayıyı desteklemek için algoyu değiştirebiliriz:

count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
    count += 1
  n /= 2  
return count

şimdi ikinci sorunun üstesinden gelmek için algo gibi yazabiliriz: -

int bit_count(int num)
{
    int count=0;
    while(num)
    {
        num=(num)&(num-1);
        count++;
    }
    return count;
}

tam referans için bakınız:

http://goursaha.freeoda.com/Miscellaneous/IntegerBitCount.html


9

Bence Brian Kernighan'ın yöntemi de faydalı olacak ... Belirlenen bitler kadar çok yinelemeden geçiyor. Yani sadece yüksek biti ayarlanmış 32 bitlik bir kelimemiz varsa, o zaman döngüden sadece bir kez geçer.

int countSetBits(unsigned int n) { 
    unsigned int n; // count the number of bits set in n
    unsigned int c; // c accumulates the total bits set in n
    for (c=0;n>0;n=n&(n-1)) c++; 
    return c; 
}

1988'de yayınlanan C Programlama Dili 2. Baskı. (Brian W. Kernighan ve Dennis M. Ritchie tarafından) 2-9. 19 Nisan 2006'da Don Knuth bana bu yöntemin "ilk olarak Peter Wegner tarafından CACM 3 (1960), 322'de yayınlandığını belirtti. (Ayrıca Derrick Lehmer tarafından bağımsız olarak keşfedildi ve 1964'te Beckenbach tarafından düzenlenen bir kitapta yayınlandı."


8

Ben daha sezgisel aşağıdaki kodu kullanın.

int countSetBits(int n) {
    return !n ? 0 : 1 + countSetBits(n & (n-1));
}

Mantık: n & (n-1) n'nin son ayarlanan bitini sıfırlar.

PS: Bunun ilginç bir çözüm de olsa O (1) çözümü olmadığını biliyorum.


bu, olduğu gibi, az sayıda bit içeren "seyrek" sayılar için iyidir O(ONE-BITS). Gerçekten de O (1) 'dir, çünkü en fazla 32 tek bit vardır.
ealfonso

7

"En iyi algoritma" ile ne demek istiyorsun? Kısa devre veya açlık kodu? Kodunuz çok zarif görünüyor ve sürekli bir yürütme süresi var. Kod da çok kısadır.

Ancak hız kod boyutu değil, büyük faktör ise, o zaman takip daha hızlı olabilir düşünüyorum:

       static final int[] BIT_COUNT = { 0, 1, 1, ... 256 values with a bitsize of a byte ... };
        static int bitCountOfByte( int value ){
            return BIT_COUNT[ value & 0xFF ];
        }

        static int bitCountOfInt( int value ){
            return bitCountOfByte( value ) 
                 + bitCountOfByte( value >> 8 ) 
                 + bitCountOfByte( value >> 16 ) 
                 + bitCountOfByte( value >> 24 );
        }

Bunun 64 bit değer için daha hızlı olmayacağını, ancak 32 bit değerin daha hızlı olabileceğini düşünüyorum.


Kodumun 10 işlemi var. Kodunuzun 12 işlemi var. Bağlantınız daha küçük dizilerle çalışır (5). 256 eleman kullanıyorum. Önbellekleme ile ilgili bir sorun olabilir. Ancak çok sık kullanırsanız, bu bir sorun değildir.
Horcrux7

Bu yaklaşım, ortaya çıktığı gibi, bit-twling yaklaşımından biraz daha hızlıdır. Daha fazla bellek kullanmaya gelince, daha az koda derlenir ve bu fonksiyon her satıra geldiğinizde tekrarlanır. Böylece kolayca net bir kazanç olabilir.

7

Yaklaşık 1990'da RISC makineleri için hızlı bir bitcount makro yazdım. Gelişmiş aritmetik (çarpma, bölme,%), bellek getirme (çok yavaş), dallar (çok yavaş) kullanmıyor, ancak CPU'nun 32-bit varil değiştirici (diğer bir deyişle, >> 1 ve >> 32 aynı döngüleri alır.) Küçük sabitlerin (6, 12, 24 gibi) kayıtlara yüklenmesinin bir maliyeti olmadığını veya depolandığını varsayar. ve tekrar tekrar kullandılar.

Bu varsayımlarla, çoğu RISC makinesinde yaklaşık 16 döngü / talimatta 32 bit sayar. 15 komutun / döngünün, döngü veya talimat sayısında bir alt sınıra yakın olduğuna dikkat edin, çünkü ekleme sayısını yarıya indirmek için en az 3 talimat (maske, vardiya, operatör) gibi görünüyor, bu nedenle log_2 (32) = 5, 5 x 3 = 15 talimatı yarı-düşüktür.

#define BitCount(X,Y)           \
                Y = X - ((X >> 1) & 033333333333) - ((X >> 2) & 011111111111); \
                Y = ((Y + (Y >> 3)) & 030707070707); \
                Y =  (Y + (Y >> 6)); \
                Y = (Y + (Y >> 12) + (Y >> 24)) & 077;

İşte ilk ve en karmaşık adımın bir sırrı:

input output
AB    CD             Note
00    00             = AB
01    01             = AB
10    01             = AB - (A >> 1) & 0x1
11    10             = AB - (A >> 1) & 0x1

eğer yukarıdaki 1. sütunu (A) alırsam, 1 bit sağa kaydırır ve AB'den çıkarırsam, çıktıyı (CD) alırım. 3 bite genişletme benzerdir; isterseniz benimki gibi 8 sıralı bir boole masa ile kontrol edebilirsiniz.

  • Don Gillies

7

C ++ kullanıyorsanız başka bir seçenek de şablon meta programlaması kullanmaktır:

// recursive template to sum bits in an int
template <int BITS>
int countBits(int val) {
        // return the least significant bit plus the result of calling ourselves with
        // .. the shifted value
        return (val & 0x1) + countBits<BITS-1>(val >> 1);
}

// template specialisation to terminate the recursion when there's only one bit left
template<>
int countBits<1>(int val) {
        return val & 0x1;
}

kullanımı:

// to count bits in a byte/char (this returns 8)
countBits<8>( 255 )

// another byte (this returns 7)
countBits<8>( 254 )

// counting bits in a word/short (this returns 1)
countBits<16>( 256 )

Tabii ki bu şablonu farklı türleri (hatta otomatik olarak algılayan bit boyutunu) kullanmak için daha da genişletebilirsiniz, ancak netlik için basit tuttum.

edit: herhangi bir C ++ derleyicide çalışması gerekir ve temelde sadece bit sayısı için sabit bir değer kullanılırsa sizin için döngü açar (bu başka bir deyişle, en hızlı genel yöntem olduğundan eminim) iyi bahsetmeyi unuttum bulacaksın)


Ne yazık ki, bit sayımı paralel olarak yapılmaz, bu yüzden muhtemelen daha yavaştır. constexprYine de güzel olabilir .
imallett

Kabul edildi - C ++ şablon özyinelemesinde eğlenceli bir egzersizdi, ama kesinlikle oldukça naif bir çözüm.
pentaphobe

6

Özellikle servet dosyasından bu örneğe düşkünüm:

#define BITCOUNT (x) (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 255)
#define BX_ (x) [(x) - [((x) >> 1) & 0x77777777)
                             - (((x) >> 2) ve 0x33333333)
                             - (((x) >> 3) ve 0x11111111))

En çok hoşuma gidiyor çünkü çok güzel!


1
Diğer önerilere kıyasla nasıl bir performans sergiliyor?
asdf

6

Java JDK1.5

Integer.bitCount (n);

burada n, 1'leri sayılacak olan sayıdır.

ayrıca kontrol et,

Integer.highestOneBit(n);
Integer.lowestOneBit(n);
Integer.numberOfLeadingZeros(n);
Integer.numberOfTrailingZeros(n);

//Beginning with the value 1, rotate left 16 times
     n = 1;
         for (int i = 0; i < 16; i++) {
            n = Integer.rotateLeft(n, 1);
            System.out.println(n);
         }

Gerçekten bir algoritma değil, bu sadece bir kütüphane çağrısı. Java için kullanışlıdır, herkes için çok fazla değildir.
benzado

2
@benzado doğru ama yine de +1, çünkü bazı Java geliştiricileri yöntemin farkında olmayabilir
finnw

@finnw, ben bu geliştiricilerden biriyim. :)
neevek

6

Ben SIMD talimatı (SSSE3 ve AVX2) kullanarak bir dizide bit sayma bir uygulama bulundu. __Popcnt64 iç işlevini kullanmasından daha 2-2,5 kat daha iyi bir performansa sahiptir.

SSSE3 sürümü:

#include <smmintrin.h>
#include <stdint.h>

const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m128i _sum =  _mm128_setzero_si128();
    for (size_t i = 0; i < size; i += 16)
    {
        //load 16-byte vector
        __m128i _src = _mm_loadu_si128((__m128i*)(src + i));
        //get low 4 bit for every byte in vector
        __m128i lo = _mm_and_si128(_src, F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
    }
    uint64_t sum[2];
    _mm_storeu_si128((__m128i*)sum, _sum);
    return sum[0] + sum[1];
}

AVX2 sürümü:

#include <immintrin.h>
#include <stdint.h>

const __m256i Z = _mm256_set1_epi8(0x0);
const __m256i F = _mm256_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m256i T = _mm256_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 
                                   0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);

uint64_t BitCount(const uint8_t * src, size_t size)
{
    __m256i _sum =  _mm256_setzero_si256();
    for (size_t i = 0; i < size; i += 32)
    {
        //load 32-byte vector
        __m256i _src = _mm256_loadu_si256((__m256i*)(src + i));
        //get low 4 bit for every byte in vector
        __m256i lo = _mm256_and_si256(_src, F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, lo)));
        //get high 4 bit for every byte in vector
        __m256i hi = _mm256_and_si256(_mm256_srli_epi16(_src, 4), F);
        //sum precalculated value from T
        _sum = _mm256_add_epi64(_sum, _mm256_sad_epu8(Z, _mm256_shuffle_epi8(T, hi)));
    }
    uint64_t sum[4];
    _mm256_storeu_si256((__m256i*)sum, _sum);
    return sum[0] + sum[1] + sum[2] + sum[3];
}

6

Bunu her zaman Rekabetçi Programlamada kullanıyorum ve yazması kolay ve verimli:

#include <bits/stdc++.h>

using namespace std;

int countOnes(int n) {
    bitset<32> b(n);
    return b.count();
}

5

Set bitlerini saymak için birçok algoritma vardır; ama en iyisinin daha hızlı olduğunu düşünüyorum! Ayrıntılı bilgileri bu sayfada görebilirsiniz:

Bit Twiddling Hacks

Bunu öneririm:

64 bit talimatları kullanarak 14, 24 veya 32 bit sözcüklerle ayarlanan bitleri sayma

unsigned int v; // count the number of bits set in v
unsigned int c; // c accumulates the total bits set in v

// option 1, for at most 14-bit values in v:
c = (v * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;

// option 2, for at most 24-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) 
     % 0x1f;

// option 3, for at most 32-bit values in v:
c =  ((v & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;
c += (((v & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL) % 
     0x1f;
c += ((v >> 24) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f;

Bu yöntem, verimli olmak için hızlı modül bölmeli 64 bit CPU gerektirir. İlk seçenek sadece 3 işlem gerektirir; ikinci seçenek 10 alır; ve üçüncü seçenek 15 alır.


5

Önceden hesaplanmış Byte bit sayım tablosunu kullanarak giriş boyutunda dallanma ile hızlı C # çözümü.

public static class BitCount
{
    public static uint GetSetBitsCount(uint n)
    {
        var counts = BYTE_BIT_COUNTS;
        return n <= 0xff ? counts[n]
             : n <= 0xffff ? counts[n & 0xff] + counts[n >> 8]
             : n <= 0xffffff ? counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff]
             : counts[n & 0xff] + counts[(n >> 8) & 0xff] + counts[(n >> 16) & 0xff] + counts[(n >> 24) & 0xff];
    }

    public static readonly uint[] BYTE_BIT_COUNTS = 
    {
        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
    };
}

İronik bir şekilde, bu tablo bu iş parçacığında yayınlanan algoritmalardan herhangi biri tarafından oluşturulmuş olabilir! Bununla birlikte, bunun gibi tabloların kullanılması sabit zamanlı performans anlamına gelir. Bir adım ileri gitmek ve 64K çeviri tablosu oluşturmak, gerekli olan AND, SHIFT ve ADD işlemlerini yarıya indirecektir. Bit manipülatörleri için ilginç bir konu!
user924272

Önbellek sorunları nedeniyle daha büyük tablolar daha yavaş olabilir (ve sabit zamanlı olmayabilir). (0xe994 >>(k*2))&3Hafıza erişimi olmadan , bir seferde 3 bit 'arayabilirsiniz'
greggo

5

İşte algoritmalarınızın her birini herhangi bir mimaride karşılaştırabilen taşınabilir bir modül (ANSI-C).

CPU'nuzda 9 bit bayt var mı? Sorun değil :-) Şu anda 2 algoritma, K&R algoritması ve bir byte wise arama tablosu uygulamaktadır. Arama tablosu, K&R algoritmasından ortalama 3 kat daha hızlıdır. Birisi "Hacker's Delight" algoritmasını taşınabilir hale getirmenin bir yolunu bulabilirse onu eklemekten çekinmeyin.

#ifndef _BITCOUNT_H_
#define _BITCOUNT_H_

/* Return the Hamming Wieght of val, i.e. the number of 'on' bits. */
int bitcount( unsigned int );

/* List of available bitcount algorithms.  
 * onTheFly:    Calculate the bitcount on demand.
 *
 * lookupTalbe: Uses a small lookup table to determine the bitcount.  This
 * method is on average 3 times as fast as onTheFly, but incurs a small
 * upfront cost to initialize the lookup table on the first call.
 *
 * strategyCount is just a placeholder. 
 */
enum strategy { onTheFly, lookupTable, strategyCount };

/* String represenations of the algorithm names */
extern const char *strategyNames[];

/* Choose which bitcount algorithm to use. */
void setStrategy( enum strategy );

#endif

.

#include <limits.h>

#include "bitcount.h"

/* The number of entries needed in the table is equal to the number of unique
 * values a char can represent which is always UCHAR_MAX + 1*/
static unsigned char _bitCountTable[UCHAR_MAX + 1];
static unsigned int _lookupTableInitialized = 0;

static int _defaultBitCount( unsigned int val ) {
    int count;

    /* Starting with:
     * 1100 - 1 == 1011,  1100 & 1011 == 1000
     * 1000 - 1 == 0111,  1000 & 0111 == 0000
     */
    for ( count = 0; val; ++count )
        val &= val - 1;

    return count;
}

/* Looks up each byte of the integer in a lookup table.
 *
 * The first time the function is called it initializes the lookup table.
 */
static int _tableBitCount( unsigned int val ) {
    int bCount = 0;

    if ( !_lookupTableInitialized ) {
        unsigned int i;
        for ( i = 0; i != UCHAR_MAX + 1; ++i )
            _bitCountTable[i] =
                ( unsigned char )_defaultBitCount( i );

        _lookupTableInitialized = 1;
    }

    for ( ; val; val >>= CHAR_BIT )
        bCount += _bitCountTable[val & UCHAR_MAX];

    return bCount;
}

static int ( *_bitcount ) ( unsigned int ) = _defaultBitCount;

const char *strategyNames[] = { "onTheFly", "lookupTable" };

void setStrategy( enum strategy s ) {
    switch ( s ) {
    case onTheFly:
        _bitcount = _defaultBitCount;
        break;
    case lookupTable:
        _bitcount = _tableBitCount;
        break;
    case strategyCount:
        break;
    }
}

/* Just a forwarding function which will call whichever version of the
 * algorithm has been selected by the client 
 */
int bitcount( unsigned int val ) {
    return _bitcount( val );
}

#ifdef _BITCOUNT_EXE_

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* Use the same sequence of pseudo random numbers to benmark each Hamming
 * Weight algorithm.
 */
void benchmark( int reps ) {
    clock_t start, stop;
    int i, j;
    static const int iterations = 1000000;

    for ( j = 0; j != strategyCount; ++j ) {
        setStrategy( j );

        srand( 257 );

        start = clock(  );

        for ( i = 0; i != reps * iterations; ++i )
            bitcount( rand(  ) );

        stop = clock(  );

        printf
            ( "\n\t%d psudoe-random integers using %s: %f seconds\n\n",
              reps * iterations, strategyNames[j],
              ( double )( stop - start ) / CLOCKS_PER_SEC );
    }
}

int main( void ) {
    int option;

    while ( 1 ) {
        printf( "Menu Options\n"
            "\t1.\tPrint the Hamming Weight of an Integer\n"
            "\t2.\tBenchmark Hamming Weight implementations\n"
            "\t3.\tExit ( or cntl-d )\n\n\t" );

        if ( scanf( "%d", &option ) == EOF )
            break;

        switch ( option ) {
        case 1:
            printf( "Please enter the integer: " );
            if ( scanf( "%d", &option ) != EOF )
                printf
                    ( "The Hamming Weight of %d ( 0x%X ) is %d\n\n",
                      option, option, bitcount( option ) );
            break;
        case 2:
            printf
                ( "Please select number of reps ( in millions ): " );
            if ( scanf( "%d", &option ) != EOF )
                benchmark( option );
            break;
        case 3:
            goto EXIT;
            break;
        default:
            printf( "Invalid option\n" );
        }

    }

 EXIT:
    printf( "\n" );

    return 0;
}

#endif

1
Eklentinizi, polimorfik yaklaşımınızı ve yeniden kullanılabilir bir kütüphane veya bağımsız, test yürütülebilir olarak inşa etmeyi çok seviyorum. Çok iyi düşünülmüş =)

5

ne yapabilirsin

while(n){
    n=n&(n-1);
    count++;
}

bunun arkasındaki mantık n-1 bitlerinin en sağdaki n bitinden ters çevrilmiş olmasıdır. n = 6 yani 110 ise 5 101'dir, bitler en sağdaki n bitinden ters çevrilir. eğer biz ve bu ikimiz her yinelemede en sağdaki bit 0'ı yapacağız ve her zaman bir sonraki en sağdaki bite gideceğiz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.