8 Bits, die die Zahl 7 darstellen, sehen folgendermaßen aus:
00000111
Es sind drei Bits gesetzt.
Was sind Algorithmen, um die Anzahl der gesetzten Bits in einer 32-Bit-Ganzzahl zu bestimmen?
Dies ist bekannt als ' Hamming Weight ', 'Popcount' oder 'Seitwärtsaddition'.
Der 'beste' Algorithmus hängt wirklich davon ab, auf welcher CPU Sie sich befinden und wie Ihr Nutzungsverhalten ist.
Einige CPUs verfügen über eine einzige integrierte Anweisung, andere dagegen über parallele Anweisungen, die auf Bitvektoren wirken. Die parallelen Anweisungen (wie popcnt
von x86, auf CPUs, auf denen sie unterstützt werden) werden fast sicher am schnellsten sein. Bei einigen anderen Architekturen kann ein langsamer Befehl mit einer Mikrocodierschleife implementiert werden, die ein Bit pro Zyklus testet (citation required).
Eine vorbefüllte Tabellensuchmethode kann sehr schnell sein, wenn Ihre CPU über einen großen Cache verfügt und/oder Sie viele dieser Anweisungen in einer engen Schleife ausführen. Es kann jedoch unter den Kosten eines "Cache-Fehlschlags" leiden, bei dem die CPU einen Teil der Tabelle aus dem Hauptspeicher holen muss.
Wenn Sie wissen, dass Ihre Bytes meistens 0 oder 1 sind, gibt es sehr effiziente Algorithmen für diese Szenarien.
Ich glaube, ein sehr guter Universalalgorithmus ist der folgende, als "paralleler" oder "SWAR-Algorithmus mit variabler Genauigkeit" bekannt. Ich habe dies in einer C-ähnlichen Pseudo-Sprache ausgedrückt. Möglicherweise müssen Sie sie anpassen, um für eine bestimmte Sprache zu funktionieren (z. B. bei Verwendung von uint32_t für C++ und >>> in Java):
int numberOfSetBits(int i)
{
// Java: 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;
}
Dies hat das beste Worst-Case-Verhalten eines der besprochenen Algorithmen, so dass alle Verwendungsmuster und -werte, die Sie darauf werfen, effizient behandelt werden.
Dieser bitweise SWAR-Algorithmus könnte parallelisiert werden, um in mehreren Vektorelementen auf einmal statt in einem einzelnen Ganzzahlregister ausgeführt zu werden, um die CPU mit SIMD zu beschleunigen, aber keinen verwendbaren Popcount-Befehl. (Beispiel: x86-64-Code, der auf einer CPU ausgeführt werden muss, nicht nur in Nehalem oder höher.)
Die beste Methode zur Verwendung von Vektoranweisungen für popcount ist jedoch normalerweise die Verwendung einer Variablen-Shuffle, um eine Tabellensuche für jeweils 4 Bits von jedem Byte parallel durchzuführen. (Die 4 Bits indizieren eine Tabelle mit 16 Einträgen, die in einem Vektorregister gespeichert ist).
Auf Intel-CPUs kann der 64-Bit-Popcnt-Befehl eine SSSE3 PSHUFB
-bitparallele Implementierung um einen Faktor von 2 übertreffen, aber nur wenn Ihr Compiler es richtig macht . Andernfalls kann SSE deutlich voraus sein. Neuere Compilerversionen kennen das popcnt false AbhängigkeitsProblem bei Intel .
Verweise:
https://graphics.stanford.edu/~seander/bithacks.html
https://en.wikipedia.org/wiki/Hamming_weight
http://gurmeet.net/puzzles/fast-bit-counting-routines/
http://aggregate.ee.engr.uky.edu/MAGIC/#Population%20Count%20(Ones%20Count)
Beachten Sie auch die integrierten Funktionen Ihrer Compiler.
Auf dem Compiler GNU können Sie beispielsweise Folgendes verwenden:
int __builtin_popcount (unsigned int x);
int __builtin_popcountll (unsigned long long x);
Im schlimmsten Fall generiert der Compiler eine Funktion. Im besten Fall gibt der Compiler eine CPU-Anweisung aus, um den gleichen Job schneller auszuführen.
Die GCC-Intrinsics funktionieren sogar auf mehreren Plattformen. Popcount wird in der x86-Architektur zum Mainstream, daher ist es sinnvoll, das Intrinsic jetzt zu verwenden. Andere Architekturen haben seit Jahren den Popcount.
Auf x86 können Sie dem Compiler mitteilen, dass er die Unterstützung für popcnt
-Anweisung mit -mpopcnt
oder -msse4.2
annehmen kann, um auch die Vektoranweisungen zu aktivieren, die in derselben Generation hinzugefügt wurden. Siehe GCC x86-Optionen . -march=nehalem
(oder -march=
für welche CPU auch immer Sie Ihren Code annehmen und einstellen möchten) könnte eine gute Wahl sein. Wenn Sie die resultierende Binärdatei auf einer älteren CPU ausführen, führt dies zu einem Fehler durch ungültige Anweisungen.
Verwenden Sie -march=native
(mit gcc, clang oder ICC), um Binaries für die Maschine zu optimieren, auf der Sie sie erstellen.
MSVC bietet eine intrinsic für die x86 popcnt
-Anweisung , aber im Gegensatz zu gcc ist dies wirklich eine intrinsische für die Hardwareanweisung und erfordert Hardwareunterstützung.
Verwenden von std::bitset<>::count()
anstelle eines integrierten
Theoretisch sollte jeder Compiler, der weiß, wie er effizient für die Ziel-CPU popcount ist, diese Funktionalität durch ISO C++ std::bitset<>
verfügbar machen. In der Praxis können Sie mit Bit-HACK AND/shift/ADD in manchen Fällen bei einigen Ziel-CPUs besser aufgehoben sein.
Bei Zielarchitekturen, bei denen Hardware-Popcount eine optionale Erweiterung ist (wie x86), verfügen nicht alle Compiler über einen std::bitset
, der diese Funktion nutzt, wenn sie verfügbar ist. Beispielsweise hat MSVC keine Möglichkeit, die Unterstützung von popcnt
zur Kompilierzeit zu aktivieren, und verwendet immer eine Tabellensuche , selbst mit /Ox /Arch:AVX
(was SSE4.2 impliziert, obwohl technisch gesehen ein separates Funktionsbit für popcnt
vorhanden ist).
Zumindest erhalten Sie etwas tragbares, das überall funktioniert. Mit gcc/clang und den richtigen Zieloptionen erhalten Sie Hardware-Popcount-Werte für Architekturen, die dies unterstützen.
#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();
}
Siehe asm von gcc, clang, icc und MSVC im Godbolt-Compiler-Explorer.
x86-64 gcc -O3 -std=gnu++11 -mpopcnt
gibt Folgendes aus:
unsigned test_short(short a) { return popcount(a); }
movzx eax, di # note zero-extension, not sign-extension
popcnt rax, rax
ret
unsigned test_int(int a) { return popcount(a); }
mov eax, edi
popcnt rax, rax
ret
unsigned test_u64(unsigned long long a) { return popcount(a); }
xor eax, eax # gcc avoids false dependencies for Intel CPUs
popcnt rax, rdi
ret
PowerPC64 gcc -O3 -std=gnu++11
gibt (für die int
arg-Version) aus:
rldicl 3,3,0,32 # zero-extend from 32 to 64-bit
popcntd 3,3 # popcount
blr
Diese Quelle ist nicht x86-spezifisch oder GNU-spezifisch, sondern kann nur für x86 mit gcc/clang/icc gut kompiliert werden.
Beachten Sie auch, dass der Rückfall von gcc für Architekturen ohne Popcount mit nur einer Anweisung eine Byteweise-Tabellensuche ist. Das ist nicht wunderbar zum Beispiel für ARM .
Meiner Meinung nach ist die "beste" Lösung diejenige, die von einem anderen Programmierer (oder dem ursprünglichen Programmierer zwei Jahre später) ohne umfangreiche Kommentare gelesen werden kann. Vielleicht möchten Sie die schnellste oder klügste Lösung, die einige bereits bereitgestellt haben, aber ich bevorzuge die Lesbarkeit der Klugheit.
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;
}
Wenn Sie mehr Geschwindigkeit wünschen (und davon ausgehen, dass Sie dies gut dokumentieren, um Ihre Nachfolger zu unterstützen), können Sie eine Tabellensuche verwenden:
// 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);
}
Obwohl diese auf bestimmten Datentypgrößen basieren, sind sie nicht so portabel. Da jedoch viele Leistungsoptimierungen ohnehin nicht portabel sind, ist dies möglicherweise kein Problem. Wenn Sie Portabilität wollen, würde ich mich an die lesbare Lösung halten.
Aus Hackers Freude, p. 66, Abbildung 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;
}
Führt in ~ 20-ish-Anweisungen (Arch-abhängig) aus, keine Verzweigung.
Hacker's Delightist entzückend! Sehr empfehlenswert.
Ich denke, der schnellste Weg - ohne Verwendung von Nachschlagetabellen und popcount - ist der folgende. Es zählt die gesetzten Bits mit nur 12 Operationen.
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;
}
Es funktioniert, weil Sie die Gesamtzahl der gesetzten Bits zählen können, indem Sie die Hälfte der gesetzten Bits teilen, die Anzahl der gesetzten Bits in beiden Hälften zählen und sie dann addieren. Bekannt auch als Divide and Conquer
-Paradigma. Lass uns ins Detail gehen ..
v = v - ((v >> 1) & 0x55555555);
Die Anzahl der Bits in zwei Bits kann 0b00
, 0b01
oder 0b10
sein. Lass uns versuchen, dies auf 2 Bits zu berechnen.
---------------------------------------------
| v | (v >> 1) & 0b0101 | v - x |
---------------------------------------------
0b00 0b00 0b00
0b01 0b00 0b01
0b10 0b01 0b01
0b11 0b01 0b10
Dies war erforderlich: Die letzte Spalte zeigt die Anzahl der gesetzten Bits in jedem 2-Bit-Paar. Wenn die Zwei-Bit-Nummer >= 2 (0b10)
ist, dann erzeugt and
0b01
, andernfalls 0b00
.
v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
Diese Aussage sollte leicht verständlich sein. Nach der ersten Operation haben wir die Anzahl der gesetzten Bits in allen zwei Bits, jetzt summieren wir diese Zählung in allen 4 Bits.
v & 0b00110011 //masks out even two bits
(v >> 2) & 0b00110011 // masks out odd two bits
Wir summieren dann das obige Ergebnis und geben uns die Gesamtzahl der gesetzten Bits in 4 Bits. Die letzte Aussage ist am schwierigsten.
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
Lass es uns weiter aufschlüsseln ...
v + (v >> 4)
Es ist der zweiten Aussage ähnlich; Stattdessen zählen wir die gesetzten Bits in 4er-Gruppen. Wir wissen - aufgrund unserer vorherigen Operationen -, dass jedes Nibble die Anzahl der gesetzten Bits enthält. Schauen wir uns ein Beispiel an. Angenommen, wir haben das Byte 0b01000010
. Dies bedeutet, dass das erste Halbbyte mit 4 Bits und das zweite mit 2bits eingestellt ist. Jetzt fügen wir diese Nibbles zusammen.
0b01000010 + 0b01000000
Es gibt uns die Anzahl der gesetzten Bits in einem Byte, im ersten Halbbyte 0b01100010
, und wir maskieren die letzten vier Bytes aller Bytes in der Zahl (verwerfen sie).
0b01100010 & 0xF0 = 0b01100000
Jetzt enthält jedes Byte die Anzahl der gesetzten Bits. Wir müssen sie alle zusammenfassen. Der Trick besteht darin, das Ergebnis mit 0b10101010
zu multiplizieren, das eine interessante Eigenschaft hat. Wenn unsere Nummer vier Bytes hat, A B C D
, ergibt sich eine neue Nummer mit diesen Bytes A+B+C+D B+C+D C+D D
. Bei einer 4-Byte-Nummer können maximal 32 Bit gesetzt werden, die als 0b00100000
dargestellt werden können.
Jetzt brauchen wir nur noch das erste Byte, das die Summe aller gesetzten Bits in allen Bytes enthält, und wir erhalten es durch >> 24
. Dieser Algorithmus wurde für 32 bit
-Wörter entwickelt, kann jedoch leicht für 64 bit
-Wörter geändert werden.
Wenn Sie Java verwenden, wird dies durch die integrierte Methode Integer.bitCount
erledigt.
Mir wurde langweilig und ich habe eine Milliarde Iterationen von drei Ansätzen gemacht. Compiler ist gcc -O3. CPU ist das, was sie in das Macbook Pro der ersten Generation stecken.
Am schnellsten geht es mit 3,7 Sekunden:
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] );
}
Der zweite Platz bezieht sich auf den gleichen Code, sucht aber 4 Byte anstelle von 2 Halbwörtern. Das dauerte etwa 5,5 Sekunden.
Der dritte Platz geht an den etwas verdrehten Ansatz der "seitlichen Hinzufügung", der 8,6 Sekunden dauerte.
Der vierte Platz geht an __builtin_popcount () von GCC mit beschämenden 11 Sekunden.
Das Zählen von einem Bit auf einmal war etwas langsamer, und es wurde mir langweilig, auf den Abschluss zu warten.
Wenn Sie also vor allem Wert auf Leistung legen, verwenden Sie den ersten Ansatz. Wenn Sie Interesse haben, aber nicht genug sind, um 64 KB RAM dafür auszugeben, verwenden Sie den zweiten Ansatz. Verwenden Sie andernfalls den lesbaren (aber langsamen) Ansatz für ein Bit nach dem anderen.
Es ist schwer, sich eine Situation vorzustellen, in der Sie den etwas verwirrenden Ansatz verwenden möchten.
Edit: Ähnliche Ergebnisse hier .
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;
}
Lassen Sie mich diesen Algorithmus erklären.
Dieser Algorithmus basiert auf dem Divide- und Conquer-Algorithmus. Angenommen, es gibt eine 8-Bit-Ganzzahl 213 (binär 11010101), arbeitet der Algorithmus folgendermaßen (jedes Mal zwei Nachbarblöcke zusammenführen):
+-------------------------------+
| 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)
+-------------------------------+
Dies ist eine der Fragen, bei denen es hilfreich ist, Ihre Mikroarchitektur zu kennen. Ich habe gerade zwei Varianten unter gcc 4.3.3 getimpt, die mit -O3 unter Verwendung von C++ - Inlines kompiliert wurden, um den Overhead von Funktionsaufrufen und eine Milliarde Iterationen zu eliminieren. Takt genau).
inline int pop2 (vorzeichenloses x, vorzeichenloses y) { x = x - ((x >> 1) & 0x55555555); y = y - ((y >> 1) & 0x55555555); x = (x & 0x33333333) + ((x >> 2) & 0x33333333); y = (y & 0x33333333) + ((y >> 2) & 0x33333333); x = (x + (x >> 4)) & 0x0F0F0F0F; y = (y + (y >> 4)) & 0x0F0F0F0F; x = x + (x >> 8); y = y + (y >> 8); x = x + (x >> 16); y = y + (y >> 16); return (x + y) & 0x000000FF; }
Der unmodifizierte Hacker's Delight benötigte 12,2 Gigazyklen. Meine parallele Version (doppelt so viele Bits) läuft in 13,0 Gigazyklen. Bei einem 2,4-GHz-Core Duo verstrichen beide zusammen für beide. 25 Gigazyklen = etwas mehr als 10 Sekunden bei dieser Taktfrequenz, daher bin ich zuversichtlich, dass mein Timing stimmt.
Dies hat mit Anweisungsabhängigkeitsketten zu tun, die für diesen Algorithmus sehr schlecht sind. Ich konnte die Geschwindigkeit wieder fast verdoppeln, indem ich ein Paar von 64-Bit-Registern verwendete. Wenn ich klug wäre und x + y etwas früher hinzufügte, konnte ich einige Schichten abschneiden. Die 64-Bit-Version mit einigen kleinen Anpassungen würde ungefähr gerade rauskommen, aber doppelt so viele Bits zählen.
Mit 128-Bit-SIMD-Registern sind es noch ein Faktor zwei, und die Befehlssätze SSE haben oft auch clevere Abkürzungen.
Es gibt keinen Grund dafür, dass der Code besonders transparent ist. Die Schnittstelle ist einfach, der Algorithmus kann an vielen Stellen online abgerufen werden und ist für umfassende Komponententests geeignet. Der Programmierer, der darauf stößt, kann sogar etwas lernen. Diese Bitoperationen sind auf Maschinenebene äußerst natürlich.
OK, ich entschied mich für die optimierte 64-Bit-Version. Für dieses eine sizeof (ohne Vorzeichen lang) == 8
inline int pop2 (vorzeichenloses langes x, vorzeichenloses langes y) { x = x - ((x >> 1) & 0x5555555555555555); y = y - ((y >> 1) & 0x5555555555555555); x = (x & 0x33333333333333) + ((x >> 2) & 0x3333333333333333; y = (y & 0x33333333333333) + ((y >> 2) & 0x3333333333333333 ; x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F0F; y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F0F; x = x + y; x = x + (x >> 8); x = x + (x >> 16); x = x + (x >> 32); Rückgabe von x & 0xFF; }
Das sieht gut aus (ich teste aber nicht sorgfältig). Jetzt liegen die Zeiten bei 10,70 Gigazyklen/14,1 Gigazyklen. Diese spätere Zahl summierte sich auf 128 Milliarden Bits und entspricht 5,9 Sekunden, die auf dieser Maschine vergangen sind. Die nicht parallele Version ist etwas schneller, weil ich im 64-Bit-Modus arbeite und 64-Bit-Register mag, die etwas besser sind als 32-Bit-Register.
Mal sehen, ob es hier ein bisschen mehr OOO Pipelining gibt. Das war etwas komplizierter, also habe ich ein bisschen getestet. Jeder Begriff allein summiert sich auf 64, alle zusammen auf 256.
inline int pop4 (unsigniertes langes x, unsigniertes langes y, unsigniertes langes u, unsigniertes langes v) { Aufzählung {m1 = 0x5555555555555555, m2 = 0x3333333333333333, m3 = 0x0F0F0F0F0F0F0F0F, m4 = 0x000000FF000000FF}; x = x - ((x >> 1) & m1); y = y - ((y >> 1) & m1); u = u - ((u >> 1) & m1); v = v - ((v >> 1) & m1); x = (x & m2) + ((x >> 2) & m2); y = (y & m2) + ((y >> 2) & m2); u = (u & m2) + ((u >> 2) & m2); v = (v & m2) + ((v >> 2) & m2); x = x + y; u = u + v; x = (x & m3) + ((x >> 4) & m3); u = (u & m3) + ((u >> 4) & m3); x = x + u; x = x + (x >> 8); x = x + (x >> 16); x = x & m4; x = x + (x >> 32); Rückgabe x & 0x000001FF; }
Ich war für einen Moment aufgeregt, aber es stellt sich heraus, dass gcc Inline-Tricks mit -O3 spielt, auch wenn ich das Inline-Keyword in einigen Tests nicht verwende. Wenn ich gcc Tricks spielen lasse, dauert eine Milliarde Aufrufe für pop4 () 12,56 Gigazyklen, aber ich stellte fest, dass es Argumente als konstante Ausdrücke faltete. Eine realistischere Zahl scheint 19,6 g für weitere 30% zu sein. Meine Testschleife sieht jetzt so aus, um sicherzustellen, dass jedes Argument unterschiedlich genug ist, um zu verhindern, dass gcc Tricks spielt.
hitime b4 = rdtsc (); für (vorzeichenlose Länge i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) sum + = pop4 (i, i ^ 1, ~ i, i | 1); hitime e4 = rdtsc (); 256 Milliarden Bits wurden in 8.17s zusammengefasst. Arbeitet auf 1,02s für 32 Millionen Bits, wie in der 16-Bit-Tabellensuche verglichen. Kann nicht direkt verglichen werden, da die andere Bank nicht die Taktfrequenz angibt, aber ich glaube, dass ich die 64-KB-Tabellenedition geknackt habe.
Update: beschlossen, das Offensichtliche zu tun und pop6 () durch Hinzufügen von vier weiteren duplizierten Zeilen zu erstellen. Kam zu 22,8 gc, 384 Milliarden Bits summierten sich in 9,5 Sekunden. Es gibt also noch 20% Jetzt bei 800 ms für 32 Milliarden Bits.
Update: decided to do the obvious and create pop6() by adding four more duplicated lines. Came out to 22.8gc, 384 billion bits summed in 9.5s elapsed. So there's another 20% Now at 800ms for 32 billion bits.
Warum nicht iterativ durch 2 teilen?
count = 0 , während n> 0 if (n% 2) == 1 count + = 1 n/= 2
Ich stimme zu, dass dies nicht das schnellste ist, aber "best" ist etwas mehrdeutig. Ich würde jedoch argumentieren, dass "das Beste" ein Element der Klarheit haben sollte
Das Bit-Twiddling von Hacker's Delight wird so viel klarer, wenn Sie die Bitmuster ausschreiben.
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;
}
Der erste Schritt addiert die geraden Bits zu den ungeraden Bits, wobei jeweils eine Summe von Bits erzeugt wird. In den anderen Schritten werden Chunks höherer Ordnung zu Chunks niedrigerer Ordnung hinzugefügt, wobei die Größe des Blocks insgesamt verdoppelt wird, bis die endgültige Zählung das gesamte Int.
Für ein glückliches Medium zwischen einer 232 Lookup-Tabelle und jedes Bit einzeln durchlaufen:
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;
}
Es ist nicht die schnellste oder beste Lösung, aber ich habe die gleiche Frage auf meine Art gefunden und habe angefangen zu denken und nachzudenken. Endlich wurde mir klar, dass dies so gemacht werden kann, wenn Sie das Problem von mathematischer Seite bekommen und eine Grafik zeichnen. Dann stellen Sie fest, dass es sich um eine Funktion handelt, die einen periodischen Teil hat, und dann erkennen Sie den Unterschied zwischen den Perioden ... also Bitte schön:
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);
}
}
Dies kann in O(k)
erfolgen, wobei k
die Anzahl der gesetzten Bits ist.
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
Die Funktion, nach der Sie suchen, wird oft als "Seitensumme" oder "Bevölkerungszahl" einer Binärzahl bezeichnet. Knuth erörtert es in Vorfascicle 1A, S. 11-12 (obwohl in Band 2, 4.6.3- (7) eine kurze Referenz vorhanden war.)
Der locus classicus ist Peter Wegners Artikel "Eine Technik zum Zählen von Einzelpersonen in einem binären Computer" aus der Communications der ACM, Band 3 (1960) Nummer 5, Seite 322 . Er gibt zwei verschiedene Algorithmen an, einen für Zahlen, die als "spärlich" (d. H. Eine kleine Anzahl von Einsen) zu erwarten sind, und einen für den umgekehrten Fall optimiert.
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;
}
Einige offene Fragen: -
wir können den Algorithmus so ändern, dass die negative Zahl wie folgt unterstützt wird:
count = 0
while n != 0
if ((n % 2) == 1 || (n % 2) == -1
count += 1
n /= 2
return count
um das zweite Problem zu überwinden, können wir den Algorithmus wie folgt schreiben: -
int bit_count(int num)
{
int count=0;
while(num)
{
num=(num)&(num-1);
count++;
}
return count;
}
für vollständige referenz siehe:
http://goursaha.freeoda.com/M Miscellaneous/IntegerBitCount.html
Ich verwende den folgenden Code, der intuitiver ist.
int countSetBits(int n) {
return !n ? 0 : 1 + countSetBits(n & (n-1));
}
Logik: n & (n-1) setzt das zuletzt gesetzte Bit von n zurück.
PS: Ich weiß, das ist keine O(1) Lösung, wenn auch eine interessante Lösung.
Ich denke, die Brian Kernighans - Methode wird auch nützlich sein ....__ Sie durchläuft so viele Iterationen, wie festgelegte Bits vorhanden sind. Wenn wir also ein 32-Bit-Word haben, bei dem nur das High-Bit gesetzt ist, wird es nur einmal durch die Schleife gehen.
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;
}
Die Programmiersprache C, 2nd Ed. (von Brian W. Kernighan und Dennis M. Ritchie) erwähnt dies in Übung 2-9. Am 19. April 2006 wies Don Knuth darauf hin, dass diese Methode "erstmals von Peter Wegner in CACM 3 (1960), 322 veröffentlicht wurde. (Auch unabhängig von Derrick Lehmer entdeckt und 1964 in einem von Beckenbach herausgegebenen Buch veröffentlicht)."
Was meinst du mit "Bester Algorithmus"? Der Kurzschlusscode oder der Schnellcode? Ihr Code sieht sehr elegant aus und hat eine konstante Ausführungszeit. Der Code ist auch sehr kurz.
Aber wenn die Geschwindigkeit der Hauptfaktor ist und nicht die Codegröße, dann denke ich, dass das Folgende schneller sein kann:
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 );
}
Ich denke, dass dies für einen 64-Bit-Wert nicht schneller ist, aber ein 32-Bit-Wert kann schneller sein.
wenn Sie C++ verwenden, können Sie die Metaprogrammierung von Vorlagen verwenden.
// 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;
}
verwendung wäre:
// 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 )
sie können diese Vorlage natürlich noch erweitern, um verschiedene Typen zu verwenden (sogar die Bitgröße für die automatische Erkennung), aber ich habe es aus Gründen der Übersichtlichkeit einfach gehalten.
edit: vergessen zu erwähnen, dass dies gut ist, da sollte in einem beliebigen C++ - Compiler funktionieren und es im Grunde nur die Schleife für Sie abrollt, wenn ein konstanter Wert für die Bitanzahl verwendet wird (mit anderen Worten: Ich bin mir ziemlich sicher, dass es die schnellste Methode ist, die Sie finden werden.
Ich habe ungefähr 1990 ein schnelles Bitcount-Makro für RISC-Maschinen geschrieben. Es verwendet keine erweiterte Arithmetik (Multiplikation, Division,%), Speicherabrufe (viel zu langsam), Verzweigungen (viel zu langsam), aber es geht davon aus, dass die CPU über ein 32-Bit-Barrel-Shifter (mit anderen Worten, >> 1 und >> 32 benötigen die gleiche Anzahl von Zyklen.) Es wird davon ausgegangen, dass kleine Konstanten (wie 6, 12, 24) nichts kosten, um in die Register geladen zu werden oder gespeichert werden in temporären und immer und immer wieder verwendet.
Mit diesen Annahmen zählt es 32 Bits in etwa 16 Zyklen/Anweisungen auf den meisten RISC-Maschinen. Beachten Sie, dass 15 Anweisungen/Zyklen nahe an einer unteren Grenze für die Anzahl der Zyklen oder Anweisungen liegen, da es anscheinend mindestens 3 Anweisungen (Maske, Shift, Operator) benötigt, um die Anzahl der Addends zu halbieren, so log_2 (32) = 5, 5 x 3 = 15 Anweisungen sind quasi niedergebunden.
#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;
Hier ist ein Geheimnis für den ersten und komplexesten Schritt:
input output
AB CD Note
00 00 = AB
01 01 = AB
10 01 = AB - (A >> 1) & 0x1
11 10 = AB - (A >> 1) & 0x1
wenn ich also die 1. Spalte (A) oben nehme, sie um 1 Bit nach rechts schiebe und sie von AB subtrahiere, erhalte ich die Ausgabe (CD). Die Erweiterung auf 3 Bit ist ähnlich; Sie können es mit einem 8-reihigen booleschen Tisch wie meiner oben überprüfen, wenn Sie möchten.
Ich verwende dies immer im Wettbewerbsprogramm und es ist einfach zu schreiben und effizient:
#include <bits/stdc++.h>
using namespace std;
int countOnes(int n) {
bitset<32> b(n);
return b.count();
}
Ich habe eine Implementierung der Bitzählung in einem Array mit SIMD-Befehl (SSSE3 und AVX2) gefunden. Es ist in 2 bis 2,5 mal besser als wenn __popcnt64 intrinsic Funktion verwendet wird.
SSSE3-Version:
#include <smmintrin.h>
#include <stdint.h>
const __m128i Z = _mm_set1_epi8(0x0);
const __m128i F = _mm_set1_epi8(0xF);
//Vector with pre-calculated bit count:
const __m128i T = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
uint64_t BitCount(const uint8_t * src, size_t size)
{
__m128i _sum = _mm128_setzero_si128();
for (size_t i = 0; i < size; i += 16)
{
//load 16-byte vector
__m128i _src = _mm_loadu_si128((__m128i*)(src + i));
//get low 4 bit for every byte in vector
__m128i lo = _mm_and_si128(_src, F);
//sum precalculated value from T
_sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, lo)));
//get high 4 bit for every byte in vector
__m128i hi = _mm_and_si128(_mm_srli_epi16(_src, 4), F);
//sum precalculated value from T
_sum = _mm_add_epi64(_sum, _mm_sad_epu8(Z, _mm_shuffle_epi8(T, hi)));
}
uint64_t sum[2];
_mm_storeu_si128((__m128i*)sum, _sum);
return sum[0] + sum[1];
}
AVX2-Version:
#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];
}
Dieses Beispiel gefällt mir besonders gut aus der Fortune-Datei:
# definieren BITCOUNT (x) ((((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 255) # definieren BX_ (x) ((x) - ((( x) >> 1) & 0x77777777) (((x) >> 2) & 0x33333333) - (((x) >> 3) & 0x11111111))
Ich mag es am besten, weil es so hübsch ist!
Java JDK1.5
Integer.bitCount (n);
dabei ist n die Zahl, deren Einsen gezählt werden sollen.
überprüfen sie auch,
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);
}
Hier ist ein tragbares Modul (ANSI-C), mit dem Sie jeden Ihrer Algorithmen in jeder Architektur vergleichen können.
Ihre CPU hat 9 Bit Bytes? Kein Problem :-) Derzeit werden 2 Algorithmen, der K & R-Algorithmus und eine byteweise Nachschlagetabelle implementiert. Die Nachschlagetabelle ist im Durchschnitt dreimal schneller als der K & R-Algorithmus. Wenn jemand einen Weg finden kann, den Algorithmus "Hacker's Delight" portabel zu machen, können Sie ihn gerne hinzufügen.
#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
Es gibt viele Algorithmen, um die gesetzten Bits zu zählen; aber ich denke, der beste ist der schnellere! Sie können die Details auf dieser Seite sehen:
Ich schlage folgendes vor:
Zählbits, die in 14, 24 oder 32-Bit-Wörtern mit 64-Bit-Anweisungen gesetzt sind
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;
Dieses Verfahren erfordert eine 64-Bit-CPU mit schneller Modulteilung, um effizient zu sein. Die erste Option erfordert nur drei Vorgänge. die zweite Option dauert 10; und die dritte Option dauert 15.
Schnelle C # -Lösung mit vorberechneten Byte-Bit-Zählwerten mit Verzweigung der Eingangsgröße.
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
};
}
32-Bit oder nicht? Ich kam gerade mit dieser Methode in Java, nachdem ich gelesen hatte " cracking the coding interview " 4th Edition - Übung 5.5 (Kap. 5: Bitmanipulation). Wenn das niedrigstwertige Bit 1 Inkrement count
ist, verschieben Sie die Ganzzahl nach rechts.
public static int bitCount( int n){
int count = 0;
for (int i=n; i!=0; i = i >> 1){
count += i & 1;
}
return count;
}
Ich denke, dieser ist intuitiver als die Lösungen mit konstantem 0x33333333, egal wie schnell sie sind. Es hängt von Ihrer Definition des "besten Algorithmus" ab.
was Sie tun können, ist
while(n){
n=n&(n-1);
count++;
}
die Logik dahinter ist, dass die Bits von n-1 vom ganz rechts gesetzten Bit von n invertiert werden. Wenn n = 6, d. h. 110, dann ist 5 101, werden die Bits vom ganz rechts gesetzten Bit von n invertiert. Wenn wir & diese beiden also das am weitesten rechts stehende Bit 0 in jeder Iteration machen und immer zum am weitesten rechts stehenden gesetzten Bit gehen. Von dort aus wird das gesetzte Bit gezählt. Die schlechteste Zeitkomplexität ist O(logn), wenn jedes Bit gesetzt ist.
Ich persönlich benutze das:
public static int myBitCount(long L){
int count = 0;
while (L != 0) {
count++;
L ^= L & -L;
}
return count;
}
int bitcount(unsigned int n)
{
int count=0;
while(n)
{
count += n & 0x1u;
n >>= 1;
}
return count;
}
Der iterierte 'count' läuft zeitlich proportional zur Gesamtzahl der Bits. Es durchläuft einfach alle Bits und endet aufgrund der while-Bedingung etwas früher. Nützlich, wenn 1'S oder die gesetzten Bits sparse und zwischen niedrigstwertige Bits sind.
Ein weiterer Hamming-Gewichtsalgorithmus, wenn Sie sich auf einer BMI2-fähigen CPU befinden
the_weight=__tzcnt_u64(~_pext_u64(data[i],data[i]));
Habe Spaß!
Sie können die integrierte Funktion __builtin_popcount () verwenden. In C++ ist kein _builtin_popcount vorhanden, es ist jedoch eine integrierte Funktion des GCC-Compilers. Diese Funktion gibt die Anzahl der gesetzten Bits in einer Ganzzahl zurück.
int __builtin_popcount (unsigned int x);
Referenz: Bit Twiddling Hacks
int countBits(int x)
{
int n = 0;
if (x) do n++;
while(x=x&(x-1));
return n;
}
Oder auch:
int countBits(int x) { return (x)? 1+countBits(x&(x-1)): 0; }
In Java 8 oder 9 rufen Sie einfach Integer.bitCount
auf.
Hier ist eine Lösung, die bisher nicht erwähnt wurde, unter Verwendung von Bitfeldern. Das folgende Programm zählt die gesetzten Bits in einem Array von 100000000-16-Bit-Ganzzahlen mit 4 verschiedenen Methoden. Die Timing-Ergebnisse sind in Klammern angegeben (unter MacOSX mit gcc -O3
):
#include <stdio.h>
#include <stdlib.h>
#define LENGTH 100000000
typedef struct {
unsigned char bit0 : 1;
unsigned char bit1 : 1;
unsigned char bit2 : 1;
unsigned char bit3 : 1;
unsigned char bit4 : 1;
unsigned char bit5 : 1;
unsigned char bit6 : 1;
unsigned char bit7 : 1;
} bits;
unsigned char sum_bits(const unsigned char x) {
const bits *b = (const bits*) &x;
return b->bit0 + b->bit1 + b->bit2 + b->bit3 \
+ b->bit4 + b->bit5 + b->bit6 + b->bit7;
}
int NumberOfSetBits(int i) {
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
#define out(s) \
printf("bits set: %lu\nbits counted: %lu\n", 8*LENGTH*sizeof(short)*3/4, s);
int main(int argc, char **argv) {
unsigned long i, s;
unsigned short *x = malloc(LENGTH*sizeof(short));
unsigned char lut[65536], *p;
unsigned short *ps;
int *pi;
/* set 3/4 of the bits */
for (i=0; i<LENGTH; ++i)
x[i] = 0xFFF0;
/* sum_bits (1.772s) */
for (i=LENGTH*sizeof(short), p=(unsigned char*) x, s=0; i--; s+=sum_bits(*p++));
out(s);
/* NumberOfSetBits (0.404s) */
for (i=LENGTH*sizeof(short)/sizeof(int), pi=(int*)x, s=0; i--; s+=NumberOfSetBits(*pi++));
out(s);
/* populate lookup table */
for (i=0, p=(unsigned char*) &i; i<sizeof(lut); ++i)
lut[i] = sum_bits(p[0]) + sum_bits(p[1]);
/* 256-bytes lookup table (0.317s) */
for (i=LENGTH*sizeof(short), p=(unsigned char*) x, s=0; i--; s+=lut[*p++]);
out(s);
/* 65536-bytes lookup table (0.250s) */
for (i=LENGTH, ps=x, s=0; i--; s+=lut[*ps++]);
out(s);
free(x);
return 0;
}
Während die Bitfield-Version sehr gut lesbar ist, zeigen die Timing-Ergebnisse, dass sie über 4x langsamer ist als NumberOfSetBits()
. Die auf Nachschlagetabellen basierenden Implementierungen sind insbesondere mit einer 65-KB-Tabelle noch ein bisschen schneller.
Hier ist der Beispielcode, der nützlich sein kann.
private static final int[] bitCountArr = new int[]{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};
private static final int firstByteFF = 255;
public static final int getCountOfSetBits(int value){
int count = 0;
for(int i=0;i<4;i++){
if(value == 0) break;
count += bitCountArr[value & firstByteFF];
value >>>= 8;
}
return count;
}
C++ 20 std::popcount
Der folgende Vorschlag wurde zusammengeführt http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0553r4.html und sollte dem <bit>
-Header hinzugefügt werden.
Ich erwarte die Verwendung wie folgt:
#include <bit>
#include <iostream>
int main() {
std::cout << std::popcount(0x55) << std::endl;
}
Ich werde es versuchen, wenn GCC unterstützt wird. GCC 9.1.0 mit g++-9 -std=c++2a
unterstützt es immer noch nicht.
In dem Vorschlag heißt es:
Header:
<bit>
namespace std { // 25.5.6, counting template<class T> constexpr int popcount(T x) noexcept;
und:
template<class T> constexpr int popcount(T x) noexcept;
Einschränkungen: T ist ein Integer-Typ ohne Vorzeichen (3.9.1 [basic.fundamental]).
Rückgabe: Die Anzahl von 1 Bits im Wert von x.
std::rotl
und std::rotr
wurden ebenfalls hinzugefügt, um kreisförmige Bit-Rotationen durchzuführen: Best Practices für kreisförmige Verschiebungs- (Rotations-) Operationen in C++
#!/user/local/bin/Perl
$c=0x11BBBBAB;
$count=0;
$m=0x00000001;
for($i=0;$i<32;$i++)
{
$f=$c & $m;
if($f == 1)
{
$count++;
}
$c=$c >> 1;
}
printf("%d",$count);
ive done it through a Perl script. the number taken is $c=0x11BBBBAB
B=3 1s
A=2 1s
so in total
1+1+3+3+3+2+3+3=19
Ich habe diesen Ansatz nirgendwo gesehen:
int nbits(unsigned char v) {
return ((((v - ((v >> 1) & 0x55)) * 0x1010101) & 0x30c00c03) * 0x10040041) >> 0x1c;
}
Es arbeitet pro Byte, also müsste es für eine 32-Bit-Ganzzahl viermal aufgerufen werden. Sie wird von der Seitwärtsaddition abgeleitet, verwendet jedoch zwei 32-Bit-Multiplikationen, um die Anzahl der Befehle auf nur 7 zu reduzieren.
Die meisten aktuellen C-Compiler optimieren diese Funktion mithilfe von SIMD (SSE2) -Anweisungen, wenn klar ist, dass die Anzahl der Anforderungen ein Vielfaches von 4 beträgt und sie durchaus wettbewerbsfähig wird. Es ist portabel, kann als Makro- oder Inline-Funktion definiert werden und benötigt keine Datentabellen.
Dieser Ansatz kann mit 64-Bit-Multiplikationen auf jeweils 16 Bits erweitert werden. Es schlägt jedoch fehl, wenn alle 16 Bits gesetzt sind, und gibt 0 zurück. Daher kann es nur verwendet werden, wenn der 0xffff-Eingabewert nicht vorhanden ist. Es ist auch langsamer aufgrund der 64-Bit-Operationen und optimiert nicht gut.
Folgendes funktioniert in PHP (alle PHP -Integer sind 32-Bit-Vorzeichen, dieses 31-Bit):
function bits_population($nInteger)
{
$nPop=0;
while($nInteger)
{
$nInteger^=(1<<(floor(1+log($nInteger)/log(2))-1));
$nPop++;
}
return $nPop;
}
Wie wäre es, die Ganzzahl in eine Binärzeichenfolge umzuwandeln und die Einsen zu zählen?
pHP-Lösung:
substr_count( decbin($integer), '1' );
Einfacher Algorithmus zum Zählen der Anzahl der gesetzten Bits:
int countbits(n){
int count = 0;
while(n != 0){
n = n & (n-1);
count++;
}
return count;
}
Nehmen Sie das Beispiel von 11 (1011) und versuchen Sie, den Algorithmus manuell zu durchlaufen. Sollte dir sehr helfen!
Ein einfacher Weg, der für eine kleine Anzahl von Bits gut funktionieren sollte, ist ungefähr so (für 4 Bits in diesem Beispiel):
(i & 1) + (i & 2)/2 + (i & 4)/4 + (i & 8)/8
Würden andere dies als einfache Lösung für eine kleine Anzahl von Bits empfehlen?