Frage

8 Bits der Nummer 7 sieht wie folgt darstellen:

00000111

Drei Bits werden.

Was sind Algorithmen, um die Anzahl der gesetzten Bits in einem 32-Bit-Integer zu bestimmen?

War es hilfreich?

Lösung

Dies ist bekannt als die ' Hamming Gewicht ', 'PopCount' oder 'seitwärts Zusatz' .

Der ‚beste‘ Algorithmus hängt wirklich davon ab, welche CPU Sie sind und was Ihre Nutzungsmuster ist.

Einige CPUs haben einen einzigen integrierten in Anweisung, es zu tun und andere haben parallel Befehle, die auf Bitvektoren wirken. Die parallelen Anweisungen (wie x86 des popcnt, auf CPUs, wo es unterstützt wird) wird mit ziemlicher Sicherheit am schnellsten. Einige andere Architekturen haben eine langsame Anweisung mit einer Mikrocode-Schleife implementiert, die einen Bit pro Zyklus Tests ( Bearbeiten ).

Eine vorge besiedelten Tabellen-Lookup-Methode kann sehr schnell sein, wenn Ihre CPU einen großen Cache und / oder Sie viele dieser Anleitung in einer engen Schleife tun. Allerdings kann es aufgrund der Kosten eines ‚Cache-Miss‘ leiden, wo die CPU einen Teil der Tabelle aus dem Hauptspeicher zu holen hat.

Wenn Sie wissen, dass Ihr Bytes meist 0'en oder meist 1en sein, dann gibt es sehr effiziente Algorithmen für diese Szenarien.

ich glaube, ein sehr guter Allzweck-Algorithmus die folgenden Ergebnisse, als ‚parallel‘ oder ‚mit variabler Genauigkeit SWAR Algorithmus‘ bekannt. Ich habe diese Sprache in einer C-ähnlichen Pseudo ausgedrückt, müssen Sie es anpassen für eine bestimmte Sprache arbeiten (zum Beispiel unter 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 von einem des Algorithmen diskutiert, so effizient mit jedem Nutzungsmuster umgehen oder Werten, die Sie an ihn werfen.


Dieser bitweise SWAR Algorithmus könnte parallelisieren auf einmal in mehreren Vektorelementen durchgeführt wird, statt in einem einzigen Integer-Register, für eine Beschleunigung auf CPUs mit SIMD aber keine nutzbare PopCount Anweisung. (Z x86-64-Code, der auf einer CPU laufen muß, nicht nur Nehalem oder später).

Allerdings ist der beste Weg Vektorbefehle für PopCount zu verwenden, ist in der Regel durch eine Variable-Shuffle mit einer Tabellensuche für 4 Bits zu einer Zeit eines jeden Bytes parallel zu tun. (Der 4-Bit-Index 16 a Eintragstabelle in einem Vektorregister gehalten werden).

Auf Intel-CPUs, der Hardware 64-Bit-popcnt Befehl kann übertrifft eine SSSE3 PSHUFB Bit-Parallel-Umsetzung um etwa einen Faktor von 2, aber nur , wenn Ihr Compiler es genau richtig bekommt. Ansonsten kann SSE kommt deutlich vor aus. Neuere Compiler-Versionen sind sich der falsche Abhängigkeit popcnt Problem auf Intel .

Referenzen:

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)

Andere Tipps

Beachten Sie auch die integrierten Funktionen Ihres Compiler.

Auf dem GNU-Compiler zum Beispiel einfach verwenden können:

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

Im schlimmsten Fall wird der Compiler einen Aufruf einer Funktion generieren. Im besten Fall wird der Compiler einen CPU-Befehl auszusenden schneller die gleiche Arbeit zu tun.

Das GCC-Spezifika sogar über mehrere Plattformen hinweg arbeiten. PopCount wird Mainstream in der x86-Architektur worden, so macht es Sinn, jetzt die intrinsischen zu beginnen. Andere Architekturen haben die PopCount seit Jahren.


Auf x86, können Sie die Compiler sagen, dass es Unterstützung für popcnt Anweisung mit -mpopcnt annehmen kann oder -msse4.2 auch die Vektorbefehle zu ermöglichen, die in der gleichen Generation hinzugefügt wurden. Siehe GCC x86 Optionen . -march=nehalem (oder was auch immer -march= CPU Sie Ihren Code wollen und stimmen dafür übernehmen) könnte eine gute Wahl sein. Das Ausführen des resultierenden binären auf einer älteren CPU in einer illegalen Instruktion Fehler führen wird.

Um Binärdateien für die Maschine optimiert machen Sie sie bauen auf, verwenden -march=native (mit gcc, Klappern oder ICC).

MSVC bietet eine intrinsische für die x86-Instruktion popcnt , aber im Gegensatz zu gcc ist es wirklich eine intrinsische für die Hardware-Anweisung und erfordert Hardware-Unterstützung.


Mit std::bitset<>::count() anstelle eines eingebauten in

In der Theorie jeder Compiler, der weiß, wie effizient für den Ziel-CPU auf PopCount sollte diese Funktionalität durch ISO C ++ aussetzen std::bitset<> . In der Praxis könnte man mit dem Bit-Hack und / Shift / ADD in einigen Fällen für einige Ziele CPUs besser dran.

Für Ziel Architekturen, bei denen Hardware PopCount ist eine optionale Erweiterung (wie x86), nicht alle Compiler haben eine std::bitset die die Vorteile der es dauert, wenn verfügbar. Zum Beispiel hat MSVC keine Möglichkeit popcnt Unterstützung bei der Kompilierung zu ermöglichen, und verwendet immer ein Tabellennachschlag , auch mit /Ox /arch:AVX (der SSE4.2 impliziert, obwohl technisch ein separates Feature Bit für popcnt ist.)

Aber wenigstens Sie etwas portable erhalten, die überall funktioniert, und mit gcc / Klappern mit den richtigen Zieloptionen, Sie Hardware PopCount für Architekturen erhalten, die sie 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, Klirren, icc und MSVC auf die Godbolt Compiler Explorer.

x86-64 gcc -O3 -std=gnu++11 -mpopcnt gibt diese:

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 aussendet (für die int arg Version):

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

Diese Quelle ist nicht x86-spezifische oder GNU-spezifische überhaupt, aber nur kompiliert gut für x86 mit gcc / Klappern / icc.

Beachten Sie auch, dass Rückfall des gcc für Architekturen ohne Single-Instruction PopCount ist eine Byte-at-a-time-Lookup-Tabelle. Dies ist nicht wunderbar für ARM, zum Beispiel .

Meiner Meinung nach ist die „beste“ Lösung ist derjenige, der von einem anderen Programmierer gelesen werden kann (oder der ursprünglichen Programmierer zwei Jahre später) ohne reichlich Kommentare. Sie können auch die schnellste oder klügste Lösung wollen, die einige bereits zur Verfügung gestellt haben, aber ich ziehe die Lesbarkeit über Klugheit jederzeit.

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 wollen (und vorausgesetzt, Sie es gut dokumentieren Sie Ihre Nachfolger helfen), könnten Sie eine Lookup-Tabelle verwendet werden:

// 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 bestimmte Datentyp Größen verlassen, so dass sie nicht tragbar sind. Da aber viele Performance-Optimierungen ohnehin nicht tragbar sind, dass möglicherweise kein Problem sein. Wenn Sie Portabilität wollen, ich auf die lesbare Lösung halten würde.

Von Hacker Delight, p. 66, Bild 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 (Bogen abhängig), ohne Verzweigung.
Hacker Delight ist delightful! Sehr zu empfehlen.

Ich denke, den schnellsten Weg, ohne Lookup-Tabellen und PopCount -ist die folgenden. Er 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 durch Teilung in zwei Hälften zählen kann, die Anzahl der gesetzten Bits in beiden Hälften zu zählen und dann das Hinzufügen sie. Auch bekannt als Divide and Conquer Paradigma. Lassen Sie uns ins Detail erhalten ..

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

Die Anzahl der Bits in zwei Bits können 0b00, 0b01 oder 0b10 werden. Versuchen wir dies auf 2 Bits zu arbeiten ..

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

Dies ist, was erforderlich war: die letzte Spalte zeigt die Anzahl der gesetzten Bits in jedem Bit-Paar. Wenn die zwei Bit-Zahl ist >= 2 (0b10) dann erzeugt and 0b01, sonst bringt es 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 jeweils zwei Bits, wir diese Zählung summieren nun in jeweils 4 Bits.

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

Wir summieren dann das obige Ergebnis, uns die Gesamtzahl der gesetzten Bits geben in 4 Bits. Die letzte Aussage ist das schwierig.

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

Lassen Sie uns brechen sie weiter ...

v + (v >> 4)

Es ist ähnlich wie die zweite Aussage; wir zählen die gesetzten Bits in Gruppen von 4 statt. Wir wissen-wegen unserer früheren Operationen-dass jeder knabbern die Anzahl der gesetzten Bits in ihm hat. Nehmen wir ein Beispiel. Angenommen, wir das Byte 0b01000010 haben. Das bedeutet, das erste Nibble hat sein 4bits gesetzt und die zweiten seine 2 Bits gesetzt haben. Nun fügen wir diese zusammen knabbert.

0b01000010 + 0b01000000

Es gibt uns die Anzahl der gesetzten Bits in einem Byte, in dem ersten knabbern 0b01100010 und deshalb maskieren wir die letzten vier Bytes aller Bytes in der Anzahl (verwerfen sie).

0b01100010 & 0xF0 = 0b01100000

Jetzt hat jedes Byte die Anzahl der gesetzten Bits in ihm. Wir brauchen sie alle zusammen addieren. Der Trick ist, das Ergebnis durch 0b10101010 zu vervielfachen, die eine interessante Eigenschaft hat. Wenn unsere Nummer vier Bytes hat, A B C D, wird es in einer neuen Nummer mit diesem Bytes A+B+C+D B+C+D C+D D führen. Eine 4-Byte-Zahl kann bis zu 32 Bits festgelegt, die als 0b00100000 dargestellt werden kann.

Alles, was wir jetzt brauchen, ist das erste Byte, das die Summe aller gesetzten Bits in allen Bytes hat, und wir bekommen es von >> 24. Dieser Algorithmus wurde entwickelt für 32 bit Worte, kann aber leicht für 64 bit Wörter geändert werden.

Wenn Sie geschehen, werden mit Hilfe von Java, die integrierte Methode Integer.bitCount wird das tun.

Ich habe gelangweilt, und zeitlich eine Milliarde Iterationen von drei Ansätzen. Compiler ist gcc O3. CPU ist, was sie in der 1. Generation Macbook Pro setzen.

Schnellste ist die folgende, in 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 geht an den gleichen Code aber bis 4 Byte statt 2 Halbwörtern suchen. Das dauerte etwa 5,5 Sekunden.

Der dritte Platz geht an die Bit-Fummeln ‚seitwärts Zusatz‘ Ansatz, der 8,6 Sekunden dauerte.

Die vierte Platz geht an GCC __builtin_popcount () bei einer beschämenden 11 Sekunden.

Das Zählen Einer-Bit-at-a-time war Ansatz waaaay langsamer, und ich habe gelangweilt darauf zu warten, es zu beenden.

Wenn Sie also über die Leistung vor allem Pflege anders dann den ersten Ansatz. Wenn Sie sich interessieren, aber nicht genug, 64 KB RAM dafür ausgeben, den zweiten Ansatz. Ansonsten verwenden Sie den lesbaren (aber langsam) Ein-Bit-at-a-time-Ansatz.

Es ist schwer, eine Situation zu denken, wo Sie den Bit-Fummel Ansatz verwenden wollen würden.

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 Divide and Conquer-Algorithmus. Angenommen, es ist ein 8-Bit-Integer-213 (11010101 binär), der Algorithmus funktioniert wie folgt (jedes Mal verschmilzt zwei Nachbarblocks):

+-------------------------------+
| 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 jener Fragen, wo es Ihre Mikro-Architektur hilft wissen. Ich timed nur zwei Varianten unter gcc 4.3.3 kompiliert mit O3 C ++ inlines Funktionsaufruf Aufwand zu beseitigen, eine Milliarde Iterationen, die laufende Summe aller Zählungen halten den Compiler, um sicherzustellen, entfernt nicht alles wichtig, mit RDTSC für Timing ( Taktzyklus genau).

inline int pop2(unsigned x, unsigned 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;
}

Die unmodifizierten Hacker Delight nahm 12,2 gigacycles. Meine parallele Version (doppelt so viele Bits gezählt) läuft in 13,0 gigacycles. 10.5s insgesamt verstrichene sowohl für die zusammen auf einem 2,4 GHz Core Duo. 25 gigacycles = etwas mehr als 10 Sekunden bei dieser Taktfrequenz, ich bin so zuversichtlich, meine Timings richtig sind.

Das hat mit der Anweisung Abhängigkeitsketten zu tun, die für diesen Algorithmus sehr schlecht sind. Ich konnte die Geschwindigkeit wieder fast verdoppeln, indem ein Paar von 64-Bit-Registern verwendet wird. In der Tat, wenn ich bin klug und hinzugefügt x + y ein wenig früher ich einige Verschiebungen abrasieren konnte. Die 64-Bit-Version mit einigen kleinen Verbesserungen kommen würde etwa noch, aber auch hier doppelt so viele Bits zählen.

Mit 128-Bit-SIMD-Register, ein weiterer Faktor zwei, und die SSE-Befehlssätze oft clevere Abkürzungen haben, auch.

Es gibt keinen Grund für den Code besonders transparent zu sein. Die Schnittstelle ist einfach, kann der Algorithmus on-line an vielen Stellen verwiesen werden, und es ist offen für umfassenden Unit-Test. Der Programmierer, der darauf stolpert vielleicht sogar etwas lernen. Diese Bit-Operationen sind sehr natürlich auf der Maschinenebene.

OK, ich Bank beschlossen, die gezwickt 64-Bit-Version. Für diesen einen sizeof (unsigned long) == 8

inline int pop2(unsigned long x, unsigned long y)
{
    x = x - ((x >> 1) & 0x5555555555555555);
    y = y - ((y >> 1) & 0x5555555555555555);
    x = (x & 0x3333333333333333) + ((x >> 2) & 0x3333333333333333);
    y = (y & 0x3333333333333333) + ((y >> 2) & 0x3333333333333333);
    x = (x + (x >> 4)) & 0x0F0F0F0F0F0F0F0F;
    y = (y + (y >> 4)) & 0x0F0F0F0F0F0F0F0F;
    x = x + y; 
    x = x + (x >> 8);
    x = x + (x >> 16);
    x = x + (x >> 32); 
    return x & 0xFF;
}

Das sieht ungefähr richtig (Ich bin nicht sorgfältig testen, obwohl). Nun kommen die Timings aus bei 10.70 gigacycles / 14.1 gigacycles. Die späte Zahl summierte 128 Milliarden Bits und entspricht 5.9s auf dieser Maschine verstrichen ist. Die nicht-parallele Version beschleunigt ein kleines bisschen, weil ich in 64-Bit-Modus ausgeführt wird und es mag 64-Bit-Register etwas besser als 32-Bit-Register.

Lassen Sie uns sehen, ob es ein bisschen mehr OOO Pipelining ist hier werden musste. Das war ein bisschen mehr beteiligt, so dass ich getestet tatsächlich ein wenig. Jeder Begriff allein Summen bis 64, alle kombinierte Summe auf 256

inline int pop4(unsigned long x, unsigned long y, 
                unsigned long u, unsigned long v)
{
  enum { m1 = 0x5555555555555555, 
         m2 = 0x3333333333333333, 
         m3 = 0x0F0F0F0F0F0F0F0F, 
         m4 = 0x000000FF000000FF };

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

Ich war für einen Moment aufgeregt, aber es stellt sich heraus, gcc mit O3 Inline-Tricks spielt, obwohl ich nicht das Schlüsselwort inline in einigen Tests mit bin. Wenn ich gcc spielen Tricks lassen, eine Milliarde Anrufe POP4 () nimmt 12.56 gigacycles, aber ich bestimmt es Argumente als konstante Ausdrücke faltete. Eine realistischere Zahl erscheint für weitere 30% Beschleunigungs-19.6gc zu sein. Meine Testschleife sieht nun wie folgt aus, um sicherzustellen, jedes Argument unterschiedlich genug ist gcc zu stoppen Tricks zu spielen.

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

256 Milliarden summierten Bits in 8.17s verstrichen ist. Ausarbeitet, um 1.02s für 32 Millionen Bits, wie in der 16-Bit-Lookup-Tabelle gebenchmarkt. Kann nicht direkt vergleichen, weil die andere Bank, keine Taktgeschwindigkeit geben, sondern sieht aus wie ich den Rotz aus der 64KB Tisch Ausgabe schlug habe, die eine tragische Verwendung von L1-Cache in erster Linie ist.

Update: beschlossen, das Offensichtliche zu tun und schaffen POP6 () durch vier Hinzufügen von mehr dupliziert Linien. Kam zu 22.8gc 384 Milliarden Bits in 9.5s verstrichene summiert. So gibt es eine weitere 20% Jetzt bei 800ms für 32 Milliarden Bits.

Warum nicht iterativ dividieren durch 2?

count = 0
while n > 0
  if (n % 2) == 1
    count += 1
  n /= 2  

Ich bin damit einverstanden, dass dies nicht die schnellste, aber „beste“ ist nicht ganz eindeutig. Ich würde behaupten, obwohl die „beste“ sollte ein Element der Klarheit

Die Freude des Hacker-Bit-Fummeln wird so viel klarer, wenn man die Bitmuster schreiben.

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 fügt die geraden Bits zu dem ungeradzahligen Bits, um eine Summe von zwei Bits in jeder Herstellung. Die anderen Schritte hinzufügen höherwertigen Brocken zu niedriger Ordnung Stücke, bis die Blockgröße den ganzen Weg zu verdoppeln, bis wir die endgültige Anzahl der gesamten int Aufnahme haben.

Für einen Mittelweg zwischen einer 2 32 Lookup-Tabelle und iteriert durch jedes Bit einzeln:

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

http://ctips.pbwiki.com/CountBits

Es ist nicht die schnellste oder beste Lösung, aber ich fand die gleiche Frage in dem Weg, und ich begann zu denken und zu denken. schließlich wurde mir klar, dass es wie dies getan werden kann, wenn man das Problem von der mathematischen Seite bekommen, und eine Grafik zeichnen, dann finden Sie, dass es sich um eine Funktion ist, die eine gewisse periodische Teil hat, und dann merkt man den Unterschied zwischen den Perioden ... so hier gehen Sie:

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, wo k ist die Anzahl der Bits gesetzt.

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

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

    return count;
}

Die Funktion Sie suchen, wird oft als die „seitlich Summe“ oder „Bevölkerungszahl“ einer binären Zahl genannt. Knuth erörtert ihn in pre-Faszikel 1A, pp11-12 (obwohl es ein kurzer Hinweis in Band 2 war, 4.6.3- (7)).

Die locus classicus ist Artikel Peter Wegner "Eine Technik für Ones in einem binären Computer-Counting", aus dem Communications of the ACM , Volume 3 (1960) Nummer 5 , Seite 322 . Er gibt zwei verschiedene Algorithmen gibt, eine für Zahlen optimiert zu erwarten sein „spärlich“ (das heißt, eine kleine Anzahl von Einsen haben) und eine für den umgekehrten Fall.

Ein paar offene Fragen: -

  1. Wenn die Zahl negativ ist dann?
  2. Wenn die Zahl 1024 ist, dann ist die „iterativ dividieren durch 2“ Methode 10-mal wiederholen.

Wir können die algo modifizieren, um die negative Zahl zu unterstützen, wie folgt: -

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

nun das zweite Problem überwinden wir die algo wie schreiben: -

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

für eine vollständige Referenz finden Sie unter:

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

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

Ich denke, die Brian Kernighan die Methode zu Nutzen sein wird ... Es geht durch so viele Iterationen wie dort gesetzten Bits sind. Wenn wir also über ein 32-Bit-Wort haben nur mit dem hohen Bit gesetzt, dann wird es geht nur einmal durch die Schlaufe.

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

Veröffentlicht im Jahr 1988, der 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 mich darauf hin, dass diese Methode „wurde in CACM 3 (1960), veröffentlicht von Peter Wegner ersten, 322. (auch unabhängig von Derrick Lehmer entdeckt und im Jahr 1964 in einem Buch herausgegeben von Beckenbach veröffentlicht.)“

Ich verwende den folgenden Code, intuitiver ist.

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

Logik: n. & (N-1) setzt die zuletzt eingestellte Bit von n

P. S:. Ich weiß, das ist nicht O (1) Lösung, wenn auch eine interessante Lösung

Was tun Sie bedeuten, mit „Bestem Algorithmus“? Der kurzgeschlossene Code oder der gefastet Code? Ihr Code sehr elegant aussehen und es hat eine konstante Ausführungszeit. Der Code ist auch sehr kurz.

Wenn aber die Geschwindigkeit der Hauptfaktor ist und nicht die Codegröße dann denke ich, die folgen kann schneller sein:

       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 nicht mehr schneller für einen 64-Bit-Wert, sondern ein 32-Bit-Wert kann schneller sein.

schrieb ich einen schnellen Bitcount Makro für RISC-Rechner in etwa 1990. Dabei spielt es keine erweiterte Arithmetik (Multiplikation, Division,%), Speicher (viel zu langsam), Zweige (viel zu langsam) holen, aber es tut übernimmt die CPU verfügt über einen 32-Bit-Barrel-Shifter (in anderen Worten, >> 1 und >> 32 nimmt die gleiche Menge an Zyklen). Es wird davon ausgegangen, dass kleine Konstanten (wie beispielsweise 6, 12, 24) nichts kostet in die Register zu laden, oder sind in Provisorien gespeichert und wiederverwendet werden immer und immer wieder.

Mit diesen Annahmen zählt er 32 Bits in etwa 16 Zyklen / Anweisungen auf den meisten RISC-Maschinen. Man beachte, dass 15 Anweisungen / Zyklen sind in der Nähe einer unteren Schranke für die Anzahl von Zyklen oder Anweisungen, weil es mindestens 3 Anweisungen (Maske, Verschiebung, operator) zu nehmen scheint die Anzahl der Summanden in zwei Hälften geschnitten, so log_2 (32) = 5, 5 x 3 = 15 Anweisungen sind ein quasi-UntereGrenze.

#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 komplexeste 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. Säule (A) oben verschieben es rechts 1 Bit, und subtrahieren sie von AB, bekomme ich die Ausgabe (CD). Die Erweiterung auf 3 Bits ist ähnlich; Sie können es mit einer 8-Reihe boolean Tabelle wie meine oben, wenn Sie es wünschen.

  • Don Gillies

Wenn Sie C ++ verwenden eine andere Option ist Metaprogrammierung zu 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;
}

Nutzung 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 natürlich könnte diese Vorlage erweitern verschiedene Arten zu verwenden (auch automatische Erkennung Bitgröße), aber ich habe es einfach für Klarheit gehalten.

Bearbeiten: vergessen zu erwähnen, das ist gut, weil es sollte Arbeiten in jedem C ++ Compiler und es im Grunde entrollt nur für Sie Ihre Schleife, wenn ein konstanter Wert für das Bit verwendet wird Zahl (mit anderen Worten, ich bin ziemlich sicher, dass es die schnellste allgemeine Methode ist Sie finden)

Ich bin besonders gern dieses Beispiel aus dem Vermögen Datei:

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

Ich mag es am besten, weil es so schön ist!

Java jdk1.5

Integer.bitCount (n);

wobei n die Zahl, deren 1'en ist gezählt werden.

auch zu prüfen,

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

fand ich eine Implementierung von Bits in einem Array Zählen unter Verwendung von SIMD-Befehl (SSSE3 und AVX2). Es hat in 2-2,5 mal bessere Leistung als wenn es intrinsische Funktion __popcnt64 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];
}

Ich benutze diese immer in Competitive Programmierung und es ist leicht zu schreiben und effizient:

#include <bits/stdc++.h>

using namespace std;

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

Es gibt viele Algorithmus, um die gesetzten Bits zu zählen; aber ich denke, das Beste ist, desto schneller ein! Sie können die detaillierten auf dieser Seite sehen:

Bit Twiddling Hacks

Ich schlage vor, diese:

Counting gesetzten Bits in 14, 24, oder 32-Bit-Wörter 64-Bit-Befehle

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 ein 64-Bit-CPU mit schnellen Modul Division effizient. Die erste Option dauert nur 3 Operationen; Die zweite Option dauert 10; und die dritte Option nimmt 15

Fast C # Lösung mit vorberechnete Tabelle von Byte Bit zählt mit Verzweigung auf Eingabegröß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
    };
}

Hier ist ein tragbares Modul (ANSI-C), wo Benchmark jedes Ihrer Algorithmen auf jeder Architektur.

Ihre CPU verfügt über 9-Bit-Bytes? Kein Problem :-) Im Moment setzt sie zwei Algorithmen, die K & R-Algorithmus und eine Byte-weise Lookup-Tabelle. Die Nachschlagtabelle ist im Durchschnitt 3 mal schneller als der K & R-Algorithmus. Wenn jemand einen Weg des „Hacker Delight“ Algorithmus portable gerne hinzufügen, um es in.

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

32-Bit oder nicht? Ich kam gerade mit dieser Methode in Java nach dem Lesen „ die Codierung Interview Cracken“ 4. Auflage exercice 5.5 (Kap 5: Bit Manipulation). Wenn das niedrigstwertige Bit 1 Inkrement count ist, dann mit der rechten verschieben die ganze Zahl ist.

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, das ein intuitiver als die Lösungen mit konstantem 0x33333333 ist, egal wie schnell sie sind. Es hängt von Ihrer Definition von "best-Algorithmus".

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top