Come contare il numero di bit impostati in un numero intero a 32 bit?


868

8 bit che rappresentano il numero 7 si presentano così:

00000111

Sono impostati tre bit.

Quali sono gli algoritmi per determinare il numero di bit impostati in un numero intero a 32 bit?


101
Questo è il peso di Hamming a proposito.
Purfideas,

11
Che cos'è un'applicazione reale per questo? (Questo non deve essere preso come una critica - sono solo curioso.)
Jonorgan

8
Calcolo del bit di parità (cercare), che è stato utilizzato come semplice rilevamento di errori nella comunicazione.
Dialecticus,

8
@Dialecticus, il calcolo di un bit di parità è più economico rispetto al calcolo del peso di Hamming
fine

15
@spookyjon Supponiamo che tu abbia un grafico rappresentato come una matrice di adiacenza, che è essenzialmente un set di bit. Se si desidera calcolare il numero di spigoli di un vertice, si riduce a calcolare il peso di Hamming di una riga nel set di bit.
fuz,

Risposte:


850

Questo è noto come " Hamming Weight ", "popcount" o "sideways addition".

L'algoritmo "migliore" dipende in realtà dalla CPU in uso e dal modello di utilizzo.

Alcune CPU hanno una singola istruzione integrata per farlo e altre hanno istruzioni parallele che agiscono su vettori di bit. Le istruzioni parallele (come x86 popcnt, sulle CPU in cui è supportato) saranno quasi sicuramente più veloci. Alcune altre architetture possono avere un'istruzione lenta implementata con un ciclo microcodificato che verifica un po 'per ciclo ( citazione necessaria ).

Un metodo di ricerca delle tabelle precompilato può essere molto veloce se la tua CPU ha una cache di grandi dimensioni e / o stai facendo molte di queste istruzioni in un ciclo stretto. Tuttavia può soffrire a causa delle spese di un 'cache miss', in cui la CPU deve recuperare parte della tabella dalla memoria principale. (Cerca ogni byte separatamente per mantenere piccola la tabella.)

Se sai che i tuoi byte saranno per lo più 0 o principalmente 1, allora ci sono algoritmi molto efficienti per questi scenari.

Credo che un ottimo algoritmo di uso generale sia il seguente, noto come algoritmo SWAR "parallelo" o "a precisione variabile". L'ho espresso in uno pseudo linguaggio simile al C, potrebbe essere necessario modificarlo per funzionare con un linguaggio specifico (ad esempio utilizzando uint32_t per C ++ e >>> in Java):

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;
}

Per JavaScript: forzare a numeri interi con |0per prestazioni: modificare la prima riga ini = (i|0) - ((i >> 1) & 0x55555555);

Questo ha il miglior comportamento nel caso peggiore di uno qualsiasi degli algoritmi discussi, quindi gestirà in modo efficiente qualsiasi modello di utilizzo o valori che gli vengono lanciati.


Come funziona questo bithack SWAR:

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

Il primo passo è una versione ottimizzata del mascheramento per isolare i bit pari / dispari, spostandoli per allinearli e aggiungendoli. Ciò comporta effettivamente 16 aggiunte separate negli accumulatori a 2 bit ( SWAR = SIMD all'interno di un registro ). Come (i & 0x55555555) + ((i>>1) & 0x55555555).

Il passo successivo prende gli otto pari / dispari di quegli accumulatori 16x a 2 bit e li aggiunge di nuovo, producendo somme 8x a 4 bit. L' i - ...ottimizzazione non è possibile questa volta, quindi maschera solo prima / dopo lo spostamento. Utilizzando la stessa 0x33...costante entrambe le volte anziché0xccc... prima di spostare è una buona cosa quando si compila per gli ISA che devono costruire separatamente costanti a 32 bit nei registri.

La fase di spostamento e aggiunta finale si (i + (i >> 4)) & 0x0F0F0F0Fallarga agli accumulatori 4x a 8 bit. Si maschera dopo l' aggiunta anziché prima, poiché il valore massimo in qualsiasi accumulatore a 4 bit è 4, se sono stati impostati tutti e 4 i bit dei corrispondenti bit di ingresso. 4 + 4 = 8 che si adatta ancora a 4 bit, quindi è impossibile trasportare tra gli elementi nibble i + (i >> 4).

Finora questo è semplicemente normale SIMD che utilizza tecniche SWAR con alcune ottimizzazioni intelligenti. Continuando con lo stesso modello per altri 2 passaggi, è possibile estendere a 2x 16 bit quindi 1x conteggi a 32 bit. Ma c'è un modo più efficiente su macchine con moltiplicazioni hardware veloci:

Una volta che abbiamo pochi "elementi" sufficienti, una moltiplicazione con una costante magica può sommare tutti gli elementi nell'elemento superiore . In questo caso elementi byte. La moltiplicazione si ottiene spostando a sinistra e aggiungendo, quindi una moltiplicazione dei x * 0x01010101risultati in x + (x<<8) + (x<<16) + (x<<24). I nostri elementi a 8 bit sono abbastanza larghi (e contengono conteggi abbastanza piccoli) che questo non produce carry in quegli 8 bit principali.

Una versione a 64 bit di questo può fare 8x elementi a 8 bit in un numero intero a 64 bit con un moltiplicatore 0x010101010101010101 ed estrarre il byte alto con >>56. Quindi non richiede ulteriori passaggi, solo costanti più ampie. Questo è ciò che GCC utilizza per i __builtin_popcountllsistemi x86 quando l'hardwarepopcnt istruzioni non sono abilitate. Se puoi usare builtin o intrinseci per questo, fallo per dare al compilatore la possibilità di fare ottimizzazioni specifiche del target.


Con SIMD completo per vettori più ampi (ad es. Conteggio di un intero array)

Questo algoritmo bitwise-SWAR potrebbe essere parallelizzato per essere eseguito in più elementi vettoriali contemporaneamente, anziché in un unico registro intero, per una velocità sulle CPU con SIMD ma nessuna istruzione popcount utilizzabile. (ad esempio codice x86-64 che deve essere eseguito su qualsiasi CPU, non solo Nehalem o successivo.)

Tuttavia, il modo migliore per utilizzare le istruzioni vettoriali per popcount è di solito usando una variabile shuffle per fare una ricerca di tabella per 4 bit alla volta di ogni byte in parallelo. (I 4 bit indicizzano una tabella di 16 voci contenuta in un registro vettoriale).

Sulle CPU Intel, l'istruzione popcnt a 64 bit hardware può superare un'implementazione bit-parallel SSSE3PSHUFB di circa un fattore 2, ma solo se il compilatore funziona correttamente . Altrimenti SSE può venire fuori in modo significativo. Le versioni più recenti del compilatore sono a conoscenza del problema della falsa dipendenza popcnt su Intel .

Riferimenti:


87
ah! adoro la funzione NumberOfSetBits (), ma buona fortuna ottenerlo tramite una revisione del codice. :-)
Jason S,

37
Forse dovrebbe usare unsigned int, per dimostrare facilmente che è privo di complicanze da morso di segno. Inoltre sarebbe uint32_tpiù sicuro, come in, ottieni quello che ti aspetti su tutte le piattaforme?
Craig McQueen,

35
@nonnb: in realtà, come scritto, il codice è difettoso e necessita di manutenzione. >>è definito dall'implementazione per valori negativi. L'argomento deve essere modificato (o cast) in unsigned, e poiché il codice è specifico per 32 bit, probabilmente dovrebbe essere in uso uint32_t.
R .. GitHub smette di aiutare ICE

6
Non è davvero magico. Sta aggiungendo set di bit ma lo fa con alcune ottimizzazioni intelligenti. Il link di Wikipedia indicato nella risposta fa un buon lavoro nel spiegare cosa sta succedendo ma andrò linea per linea. 1) Conta il numero di bit in ogni coppia di bit, inserendo quel conteggio in quella coppia di bit (avrai 00, 01 o 10); il bit "intelligente" qui è la sottrazione che evita una maschera. 2) Aggiungi coppie di quelle somme di bitpairs nei loro stuzzichini corrispondenti; niente di intelligente qui, ma ogni bocconcino avrà ora un valore 0-4. (proseguendo)
dash-tom-bang

8
Un'altra nota, ciò si estende ai registri a 64 e 128 bit semplicemente estendendo le costanti in modo appropriato. È interessante notare (per me), quelle costanti sono anche ~ 0/3, 5, 17 e 255; i primi tre sono 2 ^ n + 1. Tutto ciò ha più senso, più lo guardi e ci pensi sotto la doccia. :)
dash-tom-bang

214

Considera anche le funzioni integrate dei compilatori.

Sul compilatore GNU, ad esempio, puoi semplicemente usare:

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

Nel peggiore dei casi il compilatore genererà una chiamata a una funzione. Nel migliore dei casi il compilatore emetterà un'istruzione cpu per fare lo stesso lavoro più velocemente.

Gli intrinseci di GCC funzionano anche su più piattaforme. Popcount diventerà mainstream nell'architettura x86, quindi ha senso iniziare a usare l'intrinseca ora. Altre architetture hanno il popcount per anni.


Su x86, puoi dire al compilatore che può assumere il supporto per le popcntistruzioni -mpopcnto -msse4.2anche abilitare le istruzioni vettoriali che sono state aggiunte nella stessa generazione. Vedi le opzioni di GCC x86 . -march=nehalem(o -march=qualunque CPU si desideri assumere e ottimizzare il codice) potrebbe essere una buona scelta. L'esecuzione del binario risultante su una CPU precedente comporterà un errore di istruzione illegale.

Per rendere i binari ottimizzati per la macchina su cui li costruisci, usa -march=native (con gcc, clang o ICC).

MSVC fornisce un intrinseco per l' popcntistruzione x86 , ma a differenza di gcc è in realtà un intrinseco per l'istruzione hardware e richiede il supporto hardware.


Utilizzo std::bitset<>::count()invece di un built-in

In teoria, qualsiasi compilatore che sappia contare in modo efficiente per la CPU di destinazione dovrebbe esporre tale funzionalità tramite ISO C ++ std::bitset<>. In pratica, potresti essere meglio con il bit-hack AND / shift / ADD in alcuni casi per alcune CPU di destinazione.

Per le architetture di destinazione in cui il popcount hardware è un'estensione opzionale (come x86), non tutti i compilatori ne hanno uno std::bitsetche ne approfitta quando disponibile. Ad esempio, MSVC non ha modo di abilitare il popcntsupporto in fase di compilazione e utilizza sempre una ricerca di tabella , anche con /Ox /arch:AVX(che implica SSE4.2, anche se tecnicamente esiste un bit di funzionalità separato per popcnt.)

Ma almeno ottieni qualcosa di portatile che funziona ovunque, e con gcc / clang con le giuste opzioni di destinazione, ottieni un popcount hardware per architetture che lo supportano.

#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();
}

Guarda asm da gcc, clang, icc e MSVC sull'esploratore del compilatore Godbolt.

x86-64 gcc -O3 -std=gnu++11 -mpopcntemette questo:

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

Emissione di PowerPC64 gcc -O3 -std=gnu++11(per la intversione arg):

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

Questa fonte non è specifica per x86 o GNU, ma si compila bene solo per x86 con gcc / clang / icc.

Si noti inoltre che il fallback di gcc per architetture senza popcount a istruzione singola è una ricerca di tabella byte alla volta. Questo non è meraviglioso per ARM, per esempio .


5
Sono d'accordo che questa è una buona pratica in generale, ma su XCode / OSX / Intel l'ho trovato per generare un codice più lento rispetto alla maggior parte dei suggerimenti pubblicati qui. Vedi la mia risposta per i dettagli.

5
L'Intel i5 / i7 ha l'istruzione SSE4 POPCNT che lo fa, usando registri di uso generale. GCC sul mio sistema non emette quell'istruzione usando questo intrinseco, immagino a causa dell'opzione no -march = nehalem.
matja,

3
@matja, il mio GCC 4.4.1 emette l'istruzione popcnt se compilo con -msse4.2
Nils Pipenbrinck

74
usa c ++ std::bitset::count. dopo aver inserito questo compila in una singola __builtin_popcountchiamata.
deft_code

1
@nlucaroni Bene, sì. I tempi stanno cambiando. Ho scritto questa risposta nel 2008. Al giorno d'oggi disponiamo di popcount nativo e l'intrinseco si compilerà in un'unica istruzione assembler se la piattaforma lo consente.
Nils Pipenbrinck,

184

Secondo me, la soluzione "migliore" è quella che può essere letta da un altro programmatore (o il programmatore originale due anni dopo) senza copiosi commenti. Potresti desiderare la soluzione più veloce o più intelligente che alcuni hanno già fornito, ma preferisco la leggibilità rispetto all'intelligenza in qualsiasi momento.

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;
}

Se vuoi maggiore velocità (e supponendo che lo documenti bene per aiutare i tuoi successori), puoi utilizzare una ricerca tabella:

// 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);
}

Anche se questi si basano su dimensioni di tipi di dati specifici, quindi non sono così portatili. Tuttavia, poiché molte ottimizzazioni delle prestazioni non sono comunque portatili, ciò potrebbe non costituire un problema. Se vuoi la portabilità, mi atterrei alla soluzione leggibile.


21
Invece di dividere per 2 e commentarlo come "shift bit ...", dovresti semplicemente usare l'operatore shift (>>) e lasciare il commento.
indiv

9
non avrebbe più senso sostituirlo if ((value & 1) == 1) { count++; }con count += value & 1?
Ponkadoodle,

21
No, la soluzione migliore non è quella più leggibile in questo caso. Qui l'algoritmo migliore è il più veloce.
NikiC,

21
Questa è interamente la tua opinione, @nikic, sebbene tu sia libero di sottovalutarmi, ovviamente. Non vi era alcuna menzione nella domanda su come quantificare "migliore", le parole "prestazioni" o "veloce" non possono essere viste da nessuna parte. Ecco perché ho optato per la lettura.
paxdiablo,

3
Sto leggendo questa risposta 3 anni dopo e la trovo come la migliore risposta perché è leggibile e ha più commenti. periodo.
waka-waka-waka,

98

Da Hacker's Delight, p. 66, Figura 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;
}

Esegue in istruzioni di circa 20 ish (dipendenti dall'arco), senza diramazioni.

Hacker's Delight è delizioso! Altamente raccomandato.


8
Il metodo Java Integer.bitCount(int)utilizza questa stessa implementazione esatta.
Marco Bolis,

Avere un piccolo problema a seguire questo - come cambierebbe se ci importassimo solo dei valori a 16 bit, anziché a 32 bit?
Jeremy Blum,

Forse la delizia degli hacker è deliziosa, ma darei un bel calcio a chiunque lo chiami popinvece di population_count(o pop_cntse devi avere un'abbreviazione). @MarcoBolis Presumo che sarà vero per tutte le versioni di Java, ma ufficialmente ciò dipenderà dall'implementazione :)
Maarten Bodewes,

E questo non richiede moltiplicazioni, come il codice nella risposta accettata.
Alex

Si noti che nel generalizzare a 64 bit c'è un problema. Il risultato non può essere 64, a causa della maschera.
Albert van der Horst,

76

Penso che il modo più veloce, senza usare le tabelle di ricerca e il popcount , sia il seguente. Conta i bit impostati con solo 12 operazioni.

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;
}

Funziona perché puoi contare il numero totale di bit impostati dividendo in due metà, contando il numero di bit impostati in entrambe le metà e quindi sommandoli. Conosciuto anche come Divide and Conquerparadigma. Entriamo nel dettaglio ..

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

Il numero di bit in due bit può essere 0b00, 0b01o 0b10. Proviamo a risolverlo su 2 bit ..

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

Questo è ciò che era richiesto: l'ultima colonna mostra il conteggio dei bit impostati in ogni coppia di due bit. Se il numero a due bit viene >= 2 (0b10)quindi andprodotto 0b01, altrimenti produce 0b00.

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

Questa affermazione dovrebbe essere facile da capire. Dopo la prima operazione abbiamo il conteggio dei bit impostati in ogni due bit, ora riassumiamo quel conteggio ogni 4 bit.

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

Riassumiamo quindi il risultato precedente, dandoci il conteggio totale dei bit impostati in 4 bit. L'ultima affermazione è la più complicata.

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

Analizziamo ulteriormente ...

v + (v >> 4)

È simile alla seconda affermazione; contiamo invece i bit impostati in gruppi di 4. Sappiamo, a causa delle nostre operazioni precedenti, che ogni bocconcino ha il conteggio dei bit impostati. Diamo un esempio. Supponiamo di avere il byte 0b01000010. Significa che il primo nibble ha i suoi 4 bit impostati e il secondo ha i suoi 2 bit impostati. Ora aggiungiamo insieme questi stuzzichini.

0b01000010 + 0b01000000

Ci dà il conteggio dei bit impostati in un byte, nel primo nibble 0b01100010e quindi mascheriamo gli ultimi quattro byte di tutti i byte nel numero (scartandoli).

0b01100010 & 0xF0 = 0b01100000

Ora ogni byte contiene il conteggio dei bit impostati. Dobbiamo sommarli tutti insieme. Il trucco è moltiplicare il risultato per il 0b10101010quale ha una proprietà interessante. Se il nostro numero ha quattro byte, A B C Dsi otterrà un nuovo numero con questi byte A+B+C+D B+C+D C+D D. Un numero di 4 byte può avere un massimo di 32 bit impostati, che possono essere rappresentati come 0b00100000.

Tutto ciò di cui abbiamo bisogno ora è il primo byte che ha la somma di tutti i bit impostati in tutti i byte e lo otteniamo >> 24. Questo algoritmo è stato progettato per le 32 bitparole ma può essere facilmente modificato per le 64 bitparole.


Di cosa si c = tratta? Sembra che dovrebbe essere eliminato. Inoltre, suggerire un set di parentesi aggiuntivo A "(((v + (v >> 4)) e 0xF0F0F0F) * 0x1010101) >> 24" per evitare alcuni avvisi classici.
chux - Ripristina Monica il

4
Una caratteristica importante è che questa routine a 32 bit funziona per entrambi popcount(int v)e popcount(unsigned v). Per portabilità, considerare popcount(uint32_t v), ecc. Mi piace molto la parte * 0x1010101.
chux - Ripristina Monica il

salsa ? (libro, link, nomi degli invasori ecc.) sarebbero MOLTO benvenuti. Perché allora possiamo incollarlo nei nostri codebase con un commento da dove proviene.
v

1
Penso che per maggiore chiarezza l'ultima riga dovrebbe essere scritta come: return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;quindi non abbiamo bisogno di contare le lettere per vedere cosa stai effettivamente facendo (dal momento che hai scartato il primo 0, ho accidentalmente pensato che hai usato lo schema di bit sbagliato (capovolto) come maschera - fino a quando ho notato che ci sono solo 7 lettere e non 8).
emem

Tale moltiplicazione per 0x01010101 potrebbe essere lenta, a seconda del processore. Ad esempio, nel mio vecchio PowerBook G4, 1 moltiplicazione era lenta quanto 4 aggiunte (non così male come divisione, dove 1 divisione era lenta quanto 23 aggiunte).
George Koehler,

54

Mi sono annoiato e ho cronometrato un miliardo di iterazioni di tre approcci. Il compilatore è gcc -O3. La CPU è qualunque cosa abbiano inserito nel Macbook Pro di prima generazione.

Il più veloce è il seguente, a 3,7 secondi:

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] );
}

Il secondo posto va allo stesso codice ma cerca 4 byte invece di 2 mezze parole. Ci sono voluti circa 5,5 secondi.

Il terzo posto va all'approccio "aggiunta laterale" che ha richiesto 8,6 secondi.

Il quarto posto va a __builtin_popcount () di GCC, in 11 vergognosi secondi.

L'approccio del conteggio bit per volta era molto più lento e mi sono stufato di aspettare che si completasse.

Quindi, se ti preoccupi delle prestazioni sopra ogni altra cosa, usa il primo approccio. Se ti interessa, ma non abbastanza da spendere 64 KB di RAM, usa il secondo approccio. Altrimenti usa l'approccio leggibile (ma lento) un bit alla volta.

È difficile pensare a una situazione in cui vorresti usare l'approccio bit-twiddling.

Modifica: risultati simili qui .


49
@Mike, l'approccio basato su tabella è imbattibile se la tabella è nella cache. Ciò accade nei micro-benchmark (ad es. Fare milioni di test in un ciclo ristretto). Tuttavia, una mancanza di cache richiede circa 200 cicli e anche il popcount più ingenuo sarà più veloce qui. Dipende sempre dall'applicazione.
Nils Pipenbrinck,

10
Se non stai chiamando questa routine qualche milione di volte in un circuito ristretto, non hai motivo di preoccuparti delle sue prestazioni e potresti anche usare l'approccio ingenuo ma leggibile poiché la perdita di prestazioni sarà trascurabile. E FWIW, il LUT a 8 bit diventa hot cache entro 10-20 chiamate.

6
Non penso che sia così difficile immaginare una situazione in cui questa è una chiamata a foglia effettuata dal metodo - in realtà facendo il pesante sollevamento - nella tua app. A seconda di cos'altro sta succedendo (e threading) la versione più piccola potrebbe vincere. Sono stati scritti molti algoritmi che hanno battuto i loro colleghi a causa di una migliore località di riferimento. Perché non anche questo?
Jason,

Prova questo con clang, è molto più intelligente nell'implementazione dei builtin.
Matt Joiner,

3
GCC non emetterà istruzioni popcont a meno che non venga chiamato con -msse4.2, case che è più veloce dell '"aggiunta laterale".
lvella,

54

Se ti capita di usare Java, il metodo integrato Integer.bitCountlo farà.


Quando sun ha fornito API diverse, deve utilizzare una logica in background, giusto?
Vallabh Patade,

2
Come nota a margine, l'implementazione di Java utilizza lo stesso algoritmo sottolineato da Kevin Little .
Marco Bolis,

2
A parte l'implementazione, questo è probabilmente il messaggio più chiaro di intenti per gli sviluppatori che mantengono il codice dopo di te (o quando torni ad esso 6 mesi dopo)
divillysausages

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;
}

Lasciami spiegare questo algoritmo.

Questo algoritmo si basa su Divide and Conquer Algorithm. Supponiamo che esista un numero intero a 8 bit 213 (11010101 in binario), l'algoritmo funziona in questo modo (ogni volta unisci due blocchi vicini):

+-------------------------------+
| 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
Questo algoritmo è la versione pubblicata da Matt Howells, prima di essere ottimizzato per diventare illeggibile.
Lefteris E,

29

Questa è una di quelle domande in cui aiuta a conoscere la tua microarchitettura. Ho appena cronometrato due varianti sotto gcc 4.3.3 compilate con -O3 usando C ++ inline per eliminare l'overhead della chiamata di funzione, un miliardo di iterazioni, mantenendo la somma corrente di tutti i conteggi per garantire che il compilatore non rimuova nulla di importante, usando rdtsc per il timing ( ciclo dell'orologio preciso).

inline int pop2 (unsigned x, unsigned y)
{
    x = x - ((x >> 1) e 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);
    return (x + y) & 0x000000FF;
}

La delizia dell'hacker non modificata ha preso 12,2 gigacicli. La mia versione parallela (contando il doppio del numero di bit) gira in 13.0 gigacycles. 10.5s totali trascorsi per entrambi insieme su un Core Duo a 2,4 GHz. 25 gigacycles = poco più di 10 secondi a questa frequenza di clock, quindi sono sicuro che i miei tempi siano corretti.

Ciò ha a che fare con le catene di dipendenza delle istruzioni, che sono molto dannose per questo algoritmo. Potrei quasi raddoppiare la velocità usando una coppia di registri a 64 bit. In effetti, se fossi intelligente e aggiungessi x + ya poco prima, potrei radere alcuni cambiamenti. La versione a 64 bit con alcune piccole modifiche sarebbe risultata uniforme, ma conterebbe di nuovo il doppio dei bit.

Con i registri SIMD a 128 bit, ancora un altro fattore due, e i set di istruzioni SSE hanno spesso anche scorciatoie intelligenti.

Non c'è motivo per cui il codice sia particolarmente trasparente. L'interfaccia è semplice, l'algoritmo può essere referenziato on-line in molti luoghi ed è suscettibile di test unitari completi. Il programmatore che inciampa su di esso potrebbe persino imparare qualcosa. Queste operazioni con i bit sono estremamente naturali a livello di macchina.

OK, ho deciso di mettere in panchina la versione ottimizzata a 64 bit. Per questa dimensione di (unsigned long) == 8

inline int pop2 (unsigned long x, unsigned long y)
{
    x = x - ((x >> 1) & 0x555555555555555555);
    y = y - ((y >> 1) & 0x555555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) e 0x333333333333333333);
    y = (y & 0x3333333333333333) + ((y >> 2) & 0x333333333333333333);
    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); 
    restituisce x & 0xFF;
}

Sembra giusto (non sto testando attentamente, però). Ora i tempi escono a 10.70 gigacicli / 14.1 gigacicli. Quel numero successivo ha sommato 128 miliardi di bit e corrisponde ai 5,9 trascorsi su questa macchina. La versione non parallela accelera un po 'perché sto funzionando in modalità 64 bit e preferisce i registri a 64 bit leggermente migliori dei registri a 32 bit.

Vediamo se c'è un po 'più di pipeline di OOO da avere qui. Questo è stato un po 'più coinvolto, quindi in realtà ho provato un po'. Ogni termine da solo somma a 64, tutti sommano a 256.

inline int pop4 (unsigned long x, unsigned long y, 
                unsigned long u, unsigned long v)
{
  enum {m1 = 0x5555555555555555, 
         m2 = 0x333333333333333333, 
         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);
    restituisce x & 0x000001FF;
}

Sono stato eccitato per un momento, ma risulta che gcc sta giocando inline con -O3 anche se non sto usando la parola chiave inline in alcuni test. Quando ho lasciato giocare a gcc, un miliardo di chiamate a pop4 () richiede 12,56 gigacicli, ma ho deciso che piegava gli argomenti come espressioni costanti. Un numero più realistico sembra essere 19,6 gc per un altro 30% di accelerazione. Il mio ciclo di prova ora assomiglia a questo, assicurandomi che ogni argomento sia abbastanza diverso da impedire a gcc di giocare brutti scherzi.

   hitime b4 = rdtsc (); 
   per (unsigned long i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) 
      somma + = pop4 (i, i ^ 1, ~ i, i | 1); 
   hitime e4 = rdtsc (); 

Sono trascorsi 256 miliardi di bit sommati in 8,17 secondi. Risolve a 1,02 secondi per 32 milioni di bit come indicato nella ricerca della tabella a 16 bit. Non è possibile confrontare direttamente, perché l'altra panchina non fornisce una velocità di clock, ma sembra che io abbia schiaffeggiato la versione da tavolo da 64 KB, che è un tragico uso della cache L1 in primo luogo.

Aggiornamento: ho deciso di fare l'ovvio e creare pop6 () aggiungendo altre quattro linee duplicate. È arrivato a 22,8 gc, sono trascorsi 384 miliardi di bit sommati in 9,5 secondi. Quindi c'è un altro 20% ora a 800 ms per 32 miliardi di bit.


2
La migliore forma non assemblatrice come questa ho visto srotolate 24 parole a 32 bit alla volta. dalkescientific.com/writings/diary/popcnt.c , stackoverflow.com/questions/3693981/... , dalkescientific.com/writings/diary/archive/2008/07/05/...
Matt Joiner

28

Perché non dividere iterativamente per 2?

conteggio = 0
mentre n> 0
  if (n% 2) == 1
    contare + = 1
  n / = 2  

Sono d'accordo che questo non è il più veloce, ma "il migliore" è in qualche modo ambiguo. Direi però che il "migliore" dovrebbe avere un elemento di chiarezza


Funzionerà ed è facile da capire, ma ci sono metodi più veloci.
Matt Howells,

2
A meno che non lo facciate MOLTO , l'impatto sulle prestazioni sarebbe trascurabile. Quindi, a parità di condizioni, concordo con Daniel che "il migliore" implica "non leggere come incomprensibile".

2
Non ho deliberatamente definito "il migliore", per ottenere una varietà di metodi. Ammettiamolo se siamo scesi al livello di questo tipo di manipolazione, probabilmente stiamo cercando qualcosa di superveloce che sembra che uno scimpanzé l'abbia digitato.
Matt Howells,

6
Codice errato. Un compilatore potrebbe farne uno buono, ma nei miei test GCC no. Sostituisci (n% 2) con (n & 1); E essendo molto più veloce di MODULO. Sostituisci (n / = 2) con (n >> = 1); bitshifting molto più veloce della divisione.
Mecki,

6
@Mecki: Nei miei test, gcc (4.0, -O3) ha fatto ovvie ottimizzazioni.

26

La delizia dell'hacker La manipolazione dei bit diventa molto più chiara quando si scrivono gli schemi di bit.

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;
}

Il primo passo aggiunge i bit pari ai bit dispari, producendo una somma di bit in ciascuno di essi. Gli altri passaggi aggiungono blocchi di ordine superiore a blocchi di ordine inferiore, raddoppiando le dimensioni del blocco fino a quando non abbiamo il conteggio finale che occupa l'intero int.


3
Questa soluzione sembra avere un problema minore, legato alla precedenza dell'operatore. Per ogni termine dovrebbe essere indicato: x = (((x >> 1) & 0b01010101010101010101010101010101) + (x & 0b01010101010101010101010101010101)); (ovvero extra parentesi aggiunte).
Nopik,

21

Per un mezzo felice tra una tabella di ricerca 2 32 e l'iterazione individuale di ogni bit:

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;
}

Da http://ctips.pbwiki.com/CountBits


Non portatile. Cosa succede se la CPU ha byte a 9 bit? Sì, ci sono CPU reali come quelle là fuori ...
Robert S. Barnes,

15
@Robert S. Barnes, questa funzione funzionerà ancora. Non presuppone la dimensione della parola nativa e nessun riferimento a "byte".
finnw,

19

Questo può essere fatto in O(k), dove kè impostato il numero di bit.

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

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

    return count;
}

Questo è essenzialmente l' algoritmo di Brian Kernighan (ricordalo?), Con il piccolo cambiamento che ha usato la n &= (n-1)forma più succinta .
Adrian Mole,

17

Non è la soluzione più veloce o migliore, ma ho trovato la stessa domanda sulla mia strada e ho iniziato a pensare e pensare. finalmente mi sono reso conto che può essere fatto in questo modo se si ottiene il problema dal punto di vista matematico e si traccia un grafico, quindi si scopre che è una funzione che ha una parte periodica e quindi si comprende la differenza tra i periodi ... quindi Ecco qui:

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 mi piace che ne dici della versione di Python: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

La funzione che stai cercando è spesso chiamata "somma laterale" o "conteggio della popolazione" di un numero binario. Knuth ne discute in Pre-Fascicle 1A, pp11-12 (anche se c'era un breve riferimento nel Volume 2, 4.6.3- (7)).

Il locus classicus è l'articolo di Peter Wegner "Una tecnica per contare quelli in un computer binario", tratto da Communications of the ACM , Volume 3 (1960) Numero 5, pagina 322 . Fornisce lì due diversi algoritmi, uno ottimizzato per i numeri che dovrebbero essere "sparsi" (ovvero, ne hanno un piccolo numero) e uno per il caso opposto.


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

Poche domande aperte: -

  1. Se il numero è negativo allora?
  2. Se il numero è 1024, il metodo "dividi iterativamente per 2" ripeterà 10 volte.

possiamo modificare l'algo per supportare il numero negativo come segue: -

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

ora per superare il secondo problema possiamo scrivere l'algo come: -

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

per riferimento completo vedi:

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


9

Penso che anche il metodo di Brian Kernighan sarà utile ... Attraversa tante iterazioni quanti sono i bit impostati. Quindi, se abbiamo una parola a 32 bit con solo il bit alto impostato, passerà solo una volta nel ciclo.

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; 
}

Pubblicato nel 1988, il C Programming Language 2nd Ed. (di Brian W. Kernighan e Dennis M. Ritchie) lo menziona nell'esercizio 2-9. Il 19 aprile 2006 Don Knuth mi fece notare che questo metodo "fu pubblicato per la prima volta da Peter Wegner in CACM 3 (1960), 322. (Scoperto anche in modo indipendente da Derrick Lehmer e pubblicato nel 1964 in un libro edito da Beckenbach.)"


8

Uso il codice seguente che è più intuitivo.

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

Logica: n & (n-1) ripristina l'ultimo bit impostato di n.

PS: So che questa non è una soluzione O (1), sebbene sia una soluzione interessante.


questo è buono per i numeri "sparsi" con un basso numero di bit, per così dire O(ONE-BITS). È davvero O (1) poiché ci sono al massimo 32 bit a uno.
ealfonso,

7

Cosa intendi con "Il miglior algoritmo"? Il codice abbreviato o il codice digiuno? Il tuo codice sembra molto elegante e ha un tempo di esecuzione costante. Anche il codice è molto breve.

Ma se la velocità è il fattore principale e non la dimensione del codice, penso che il seguente possa essere più veloce:

       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 );
        }

Penso che questo non sarà più veloce per un valore di 64 bit ma un valore di 32 bit può essere più veloce.


Il mio codice ha 10 operazioni. Il tuo codice ha 12 operazioni. Il collegamento funziona con array più piccoli (5). Uso 256 elementi. Con la memorizzazione nella cache può essere un problema. Ma se lo usi molto frequentemente, questo non è un problema.
Horcrux7,

Questo approccio è misurabile un po 'più veloce dell'approccio bit-twiddling, come risulta. Per quanto riguarda l'utilizzo di più memoria, si compila in meno codice e quel guadagno viene ripetuto ogni volta che si incorpora la funzione. Quindi potrebbe facilmente rivelarsi una vittoria netta.

7

Ho scritto una macro di bitcount veloce per macchine RISC nel 1990 circa. Non usa l'aritmetica avanzata (moltiplicazione, divisione,%), recuperi di memoria (troppo lenti), rami (troppo lenti), ma suppone che la CPU abbia un Il cambio del barilotto a 32 bit (in altre parole, >> 1 e >> 32 richiede la stessa quantità di cicli). Si presume che le piccole costanti (come 6, 12, 24) non costino nulla da caricare nei registri o vengano memorizzate nei provvisori e riutilizzato più volte.

Con questi presupposti, conta 32 bit in circa 16 cicli / istruzioni sulla maggior parte delle macchine RISC. Si noti che 15 istruzioni / cicli si avvicinano a un limite inferiore del numero di cicli o istruzioni, poiché sembra che occorrano almeno 3 istruzioni (maschera, spostamento, operatore) per dimezzare il numero di addend, quindi log_2 (32) = 5, 5 x 3 = 15 istruzioni è un limite quasi inferiore.

#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;

Ecco un segreto per il primo e più complesso passaggio:

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

quindi se prendo la prima colonna (A) sopra, la sposto a destra di 1 bit e la sottraggo da AB, ottengo l'output (CD). L'estensione a 3 bit è simile; puoi controllarlo con un tavolo booleano a 8 file come il mio sopra, se lo desideri.

  • Don Gillies

7

se stai usando C ++ un'altra opzione è usare la metaprogrammazione dei template:

// 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;
}

l'utilizzo sarebbe:

// 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 )

puoi ovviamente espandere ulteriormente questo modello per usare tipi diversi (anche con la dimensione dei bit con rilevazione automatica) ma l'ho tenuto semplice per chiarezza.

modifica: ho dimenticato di menzionare che è buono perché dovrebbe funzionare in qualsiasi compilatore C ++ e sostanzialmente srotola il tuo ciclo per te se viene usato un valore costante per il conteggio dei bit (in altre parole, sono abbastanza sicuro che sia il metodo generale più veloce lo troverai)


Sfortunatamente, il conteggio dei bit non viene eseguito in parallelo, quindi è probabilmente più lento. Potrebbe rendere piacevole constexprcomunque.
imallett,

D'accordo - è stato un esercizio divertente nella ricorsione del modello C ++, ma sicuramente una soluzione abbastanza ingenua.
pentaphobe

6

Sono particolarmente affezionato a questo esempio dal dossier della fortuna:

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

Mi piace di più perché è così carino!


1
Come si comporta rispetto agli altri suggerimenti?
asdf,

6

Java JDK1.5

Integer.bitCount (n);

dove n è il numero di cui 1 deve essere contato.

controlla anche,

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);
         }

Non proprio un algoritmo, questa è solo una chiamata in libreria. Utile per Java, non tanto per tutti gli altri.
benzado,

2
@benzado ha ragione ma +1 comunque, perché alcuni sviluppatori Java potrebbero non essere a conoscenza del metodo
finnw

@finnw, sono uno di quegli sviluppatori. :)
Neevek,

6

Ho trovato un'implementazione del conteggio dei bit in un array con l'utilizzo delle istruzioni SIMD (SSSE3 e AVX2). Ha prestazioni 2-2,5 volte migliori rispetto a quando utilizzerà la funzione intrinseca __popcnt64.

Versione SSSE3:

#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];
}

Versione AVX2:

#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

Lo uso sempre nella programmazione competitiva ed è facile da scrivere ed efficiente:

#include <bits/stdc++.h>

using namespace std;

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

5

Esistono molti algoritmi per contare i bit impostati; ma penso che il migliore sia il più veloce! Puoi vedere i dettagli in questa pagina:

Bit Twiddling Hacks

Suggerisco questo:

Bit di conteggio impostati in parole di 14, 24 o 32 bit usando le istruzioni a 64 bit

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;

Questo metodo richiede una CPU a 64 bit con divisione veloce del modulo per essere efficiente. La prima opzione richiede solo 3 operazioni; la seconda opzione richiede 10; e la terza opzione richiede 15.


5

Soluzione C # rapida che utilizza una tabella precalcolata dei conteggi dei bit byte con ramificazione sulla dimensione di input.

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
    };
}

Ironia della sorte, quella tabella avrebbe potuto essere creata da uno qualsiasi degli algoritmi pubblicati in questa discussione! Tuttavia, l'utilizzo di tabelle come questa significa prestazioni a tempo costante. Fare un ulteriore passo avanti e creare una tabella di traduzione da 64 KB dimezzerebbe quindi le operazioni AND, SHIFT e ADD. Un argomento interessante per manipolatori di bit!
user924272

Le tabelle più grandi possono essere più lente (e non a tempo costante) a causa di problemi di cache. Puoi "cercare" 3 bit alla volta con (0xe994 >>(k*2))&3, senza accesso alla memoria ...
Greggo,

5

Ecco un modulo portatile (ANSI-C) che può confrontare ciascuno dei tuoi algoritmi su qualsiasi architettura.

La tua CPU ha byte a 9 bit? Nessun problema :-) Al momento implementa 2 algoritmi, l'algoritmo K&R e una tabella di ricerca basata sul byte. La tabella di ricerca è in media 3 volte più veloce dell'algoritmo K&R. Se qualcuno riesce a trovare un modo per rendere l'algoritmo portatile "Hacker's Delight" libero di aggiungerlo.

#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
Mi piace molto il tuo approccio polimorfico e plug-in, nonché il passaggio da costruire come libreria riutilizzabile o eseguibile di prova autonomo. Molto ben pensato =)

5

quello che puoi fare è

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

la logica dietro questo è che i bit di n-1 sono invertiti dal bit impostato più a destra di n. se n = 6, ovvero 110, allora 5 è 101, i bit vengono invertiti dal bit impostato più a destra di n. quindi se noi e questi due creeremo il bit 0 più a destra in ogni iterazione e andremo sempre al bit impostato più a destra successivo. Quindi, contando il bit impostato. La peggior complessità temporale sarà O (logn) quando ogni bit è impostato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.