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?
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?
Risposte:
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 |0
per 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.
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)) & 0x0F0F0F0F
allarga 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 * 0x01010101
risultati 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_popcountll
sistemi 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.
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:
unsigned int
, per dimostrare facilmente che è privo di complicanze da morso di segno. Inoltre sarebbe uint32_t
più sicuro, come in, ottieni quello che ti aspetti su tutte le piattaforme?
>>
è 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
.
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 popcnt
istruzioni -mpopcnt
o -msse4.2
anche 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' popcnt
istruzione 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::bitset
che ne approfitta quando disponibile. Ad esempio, MSVC non ha modo di abilitare il popcnt
supporto 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 -mpopcnt
emette 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 int
versione 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 .
std::bitset::count
. dopo aver inserito questo compila in una singola __builtin_popcount
chiamata.
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.
if ((value & 1) == 1) { count++; }
con count += value & 1
?
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.
Integer.bitCount(int)
utilizza questa stessa implementazione esatta.
pop
invece di population_count
(o pop_cnt
se devi avere un'abbreviazione). @MarcoBolis Presumo che sarà vero per tutte le versioni di Java, ma ufficialmente ciò dipenderà dall'implementazione :)
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 Conquer
paradigma. Entriamo nel dettaglio ..
v = v - ((v >> 1) & 0x55555555);
Il numero di bit in due bit può essere 0b00
, 0b01
o 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 and
prodotto 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 0b01100010
e 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 0b10101010
quale ha una proprietà interessante. Se il nostro numero ha quattro byte, A B C D
si 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 bit
parole ma può essere facilmente modificato per le 64 bit
parole.
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.
popcount(int v)
e popcount(unsigned v)
. Per portabilità, considerare popcount(uint32_t v)
, ecc. Mi piace molto la parte * 0x1010101.
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).
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 .
Se ti capita di usare Java, il metodo integrato Integer.bitCount
lo farà.
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)
+-------------------------------+
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.
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
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.
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;
}
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;
}
n &= (n-1)
forma più succinta .
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);
}
}
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))()
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.
Poche domande aperte: -
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
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.)"
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.
O(ONE-BITS)
. È davvero O (1) poiché ci sono al massimo 32 bit a uno.
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.
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.
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)
constexpr
comunque.
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!
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);
}
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];
}
Esistono molti algoritmi per contare i bit impostati; ma penso che il migliore sia il più veloce! Puoi vedere i dettagli in questa pagina:
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.
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
};
}
(0xe994 >>(k*2))&3
, senza accesso alla memoria ...
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
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.