Question

8 bits représentant le nombre 7 ressemblent à ceci:

00000111

Trois bits sont définis.

Quels sont les algorithmes permettant de déterminer le nombre de bits définis dans un entier de 32 bits?

Était-ce utile?

La solution

Ceci est connu sous le nom de poids de Hamming , de "popcount" ou d '"addition latérale" .

Le "meilleur" algorithme dépend vraiment du processeur sur lequel vous vous trouvez et de votre modèle d'utilisation.

Certains processeurs ont une seule instruction intégrée pour le faire et d'autres ont des instructions parallèles qui agissent sur des vecteurs de bits. Les instructions parallèles (comme celles de x86 popcnt sur les processeurs sur lesquels elle est prise en charge) seront certainement les plus rapides. Certaines autres architectures peuvent avoir une instruction lente implémentée avec une boucle microcodée qui teste un bit par cycle ( citation nécessaire ).

Une méthode de recherche de table préremplie peut être très rapide si votre CPU dispose d'un cache volumineux et / ou si vous suivez beaucoup de ces instructions dans une boucle serrée. Toutefois, cela peut être pénalisant en raison des dépenses occasionnées par un «cache miss», dans lequel le processeur doit extraire une partie de la table de la mémoire principale.

Si vous savez que vos octets seront principalement composés de 0 ou de 1, il existe des algorithmes très efficaces pour ces scénarios.

Je crois qu'un très bon algorithme à usage général est le suivant, appelé "algorithme SWAR parallèle ou à précision variable". Je l'ai exprimé dans un pseudo langage de type C, vous devrez peut-être l'ajuster pour fonctionner pour un langage particulier (par exemple, en utilisant uint32_t pour C ++ et & Gt; & Gt; & Gt; en 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;
}

Il s’agit du meilleur comportement dans le pire des cas de l’un des algorithmes décrits, ce qui permettra de traiter efficacement tout modèle d’utilisation ou toutes les valeurs que vous lui soumettez.

Cet algorithme au niveau du bit au format SWAR pourrait être mis en parallèle dans plusieurs éléments vectoriels à la fois, plutôt que dans un seul registre entier, pour accélérer les CPU avec SIMD mais pas d'instruction popcount utilisable. (Par exemple, un code x86-64 devant s’exécuter sur n’importe quel processeur, pas uniquement Nehalem ou ultérieur.)

Cependant, la meilleure façon d'utiliser des instructions vectorielles pour popcount consiste généralement à utiliser une méthode de lecture aléatoire pour effectuer une recherche de table sur 4 bits à la fois de chaque octet en parallèle. (Les 4 bits indexent une table de 16 entrées dans un registre vectoriel).

Sur les processeurs Intel, l’instruction 64 bits popcnt matérielle peut être plus performante que SSSE3 PSHUFB bit- mise en œuvre parallèle d'un facteur 2 environ, mais seulement si votre compilateur le fait parfaitement bien . Sinon, l'ESS peut sortir nettement en avance. Les versions les plus récentes du compilateur connaissent l'existence de la dépendance Popcnt false problème sur Intel .

Références:

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)

Autres conseils

Prenez également en compte les fonctions intégrées de vos compilateurs.

Sur le compilateur GNU par exemple, vous pouvez simplement utiliser:

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

Dans le pire des cas, le compilateur générera un appel à une fonction. Dans le meilleur des cas, le compilateur émettra une instruction cpu pour effectuer le même travail plus rapidement.

Les composants intrinsèques de GCC fonctionnent même sur plusieurs plates-formes. Popcount deviendra la norme dans l'architecture x86, il est donc logique de commencer à utiliser l'intrinsèque maintenant. D'autres architectures ont le popcount depuis des années.

Sur x86, vous pouvez indiquer au compilateur qu'il peut assumer la prise en charge de l'instruction popcnt avec -mpopcnt ou -msse4.2 afin d'activer également les instructions de vecteur ajoutées à la même génération. Consultez les options GCC x86 . -march=nehalem (ou -march= quel que soit le processeur que votre code doit assumer et optimiser) peut être un bon choix. L'exécution du fichier binaire résultant sur un processeur plus ancien entraînera une erreur d'instruction illégale.

Pour optimiser les fichiers binaires pour la machine sur laquelle vous les avez construits, utilisez -march=native (avec gcc, clang ou ICC).

MSVC fournit un élément intrinsèque pour l'instruction x86 std::bitset<>::count() , mais contrairement à gcc. c’est vraiment intrinsèque à l’instruction de matériel et nécessite un support matériel.

Utilisation de std::bitset<> au lieu d'un intégré

En théorie, tout compilateur qui sait comment décompter efficacement le CPU cible devrait exposer cette fonctionnalité via ISO C ++ std::bitset . En pratique, mieux vaut utiliser le bit-hack AND / shift / ADD pour certains processeurs cibles.

Pour les architectures cibles où popcount matériel est une extension facultative (comme x86), tous les compilateurs ne disposent pas d'un /Ox /arch:AVX qui en tire parti lorsqu'il est disponible. Par exemple, MSVC n'a aucun moyen d'activer gcc -O3 -std=gnu++11 -mpopcnt la prise en charge au moment de la compilation et utilise toujours une recherche dans une table , même avec gcc -O3 -std=gnu++11 (ce qui implique SSE4.2, bien que techniquement, il existe un bit de fonctionnalité distinct pour int.)

Mais au moins, vous obtenez quelque chose de portable qui fonctionne partout, et avec gcc / clang avec les bonnes options de cibles, vous obtenez un décompte matériel pour les architectures qui le prennent en charge.

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

Voir 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 <=> émet (pour la <=> version arg):

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

Cette source n'est pas du tout spécifique à x86 ou à GNU, mais compile bien uniquement pour x86 avec gcc / clang / icc.

Notez également que la solution de remplacement de gcc pour les architectures sans popcount à instruction unique est une recherche de table octet à la fois. Ce n'est pas merveilleux pour ARM, par exemple .

À mon avis, le & "meilleur &"; La solution est celle qui peut être lue par un autre programmeur (ou le programmeur original deux ans plus tard) sans commentaires copieux. Vous voudrez peut-être la solution la plus rapide ou la plus intelligente que certains ont déjà fournie, mais je préfère la lisibilité à tout moment.

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

Si vous voulez plus de rapidité (et en supposant que vous la documentiez bien pour aider vos successeurs), vous pouvez utiliser une table de recherche:

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

Bien que ceux-ci reposent sur des tailles de type de données spécifiques, ils ne sont pas aussi portables. Toutefois, étant donné que de nombreuses optimisations de performances ne sont de toute façon pas portables, cela peut ne pas être un problème. Si vous voulez la portabilité, je me contenterais de la solution lisible.

Du délice de pirates, p. 66, Figure 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;
}

Exécute en environ 20 instructions (dépendant de l'arch), sans branche.

Le plaisir des pirates est ravissant! Fortement recommandé.

Je pense que le moyen le plus rapide & # 8212; sans utiliser de tables de recherche et popcount & # 8212; est le suivant. Il compte les bits définis avec seulement 12 opérations.

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

Cela fonctionne parce que vous pouvez compter le nombre total de bits définis en les divisant en deux moitiés, en comptant le nombre de bits définis dans les deux moitiés, puis en les additionnant. Aussi connu sous le nom de Divide and Conquer paradigme. Entrons dans les détails.

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

Le nombre de bits dans deux bits peut être 0b00, 0b01 ou 0b10. Essayons de résoudre ce problème sur 2 bits.

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

Voici ce qui était requis: la dernière colonne indique le nombre de bits définis dans chaque paire de deux bits. Si le nombre de deux bits est >= 2 (0b10), alors and produit 0b01000010, sinon il produit 0b01100010.

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

Cette déclaration doit être facile à comprendre. Après la première opération, nous avons le nombre de bits définis tous les deux bits. Maintenant, nous additionnons ce nombre tous les 4 bits.

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

Nous résumons ensuite le résultat ci-dessus en nous donnant le nombre total de bits définis sur 4 bits. La dernière déclaration est la plus délicate.

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

Décomposons davantage ...

v + (v >> 4)

Il est similaire à la deuxième déclaration. nous comptons à la place les bits définis dans des groupes de 4. Nous savons & # 8212; à cause de nos opérations précédentes & # 8212, que chaque quartet contient le nombre de bits définis. Regardons un exemple. Supposons que nous ayons l'octet 0b10101010. Cela signifie que le premier quartet a ses 4 bits et le second 2 bits. Maintenant, nous ajoutons ces grignotements ensemble.

0b01000010 + 0b01000000

Il nous donne le nombre de bits définis dans un octet, dans le premier quartet A B C D et, par conséquent, nous masquons les quatre derniers octets de tous les octets du nombre (en les supprimant).

0b01100010 & 0xF0 = 0b01100000

Chaque octet contient maintenant le nombre de bits définis. Nous devons les additionner tous ensemble. L'astuce consiste à multiplier le résultat par A+B+C+D B+C+D C+D D qui possède une propriété intéressante. Si notre numéro a quatre octets, 0b00100000, cela entraînera un nouveau numéro avec ces octets >> 24. Un nombre de 4 octets peut avoir un maximum de 32 bits, ce qui peut être représenté par 32 bit.

Tout ce dont nous avons besoin maintenant, c’est du premier octet contenant la somme de tous les bits définis dans tous les octets. Nous l’obtenons par 64 bit. Cet algorithme a été conçu pour <=> mots mais peut être facilement modifié pour <=> mots.

Si vous utilisez Java, la méthode intégrée Integer.bitCount le fera.

Je me suis ennuyé, et chronométré un milliard d'itérations de trois approches. Le compilateur est gcc -O3. Le CPU est ce qu’ils ont mis dans le Macbook Pro 1ère génération.

Le plus rapide est le suivant, à 3,7 secondes:

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

La deuxième place revient au même code mais en recherchant 4 octets au lieu de 2 demi-mots. Cela a pris environ 5,5 secondes.

La troisième place revient à l'approche "d'addition latérale" qui a pris 8,6 secondes.

La quatrième place revient à __builtin_popcount () de GCC, après 11 secondes honteuses.

L’approche consistant à compter un bit à la fois a été ralentie et je me suis ennuyé d’attendre la fin.

Donc, si vous vous souciez avant tout de la performance, utilisez la première approche. Si vous y tenez, mais pas assez pour dépenser 64 Ko de RAM dessus, utilisez la deuxième approche. Sinon, utilisez l'approche lisible (mais lente), bit par bit.

Il est difficile de penser à une situation dans laquelle vous voudriez utiliser l'approche du bidouillage.

Modifier: Résultats similaires ici .

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

Laissez-moi vous expliquer cet algorithme.

Cet algorithme est basé sur l’algorithme Divide and Conquer. Supposons qu'il existe un entier 8bit 213 (11010101 en binaire), l'algorithme fonctionne comme suit (à chaque fois que vous fusionnez deux blocs voisins):

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

C’est l’une de ces questions où il est utile de connaître votre micro-architecture. Je viens de chronométrer deux variantes de la version 4.3.3 de gcc compilées avec -O3 en utilisant des lignes C ++ pour éliminer le temps système d’appel de la fonction, un milliard d’itérations, en conservant la somme courante de tous les comptes pour que le compilateur ne supprime rien d’important, en utilisant rdtsc pour la synchronisation ( cycle d'horloge précis).

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

Le Hacker's Delight non modifié a pris 12,2 gigacycles. Ma version parallèle (qui compte deux fois plus de bits) fonctionne en 13,0 gigacycles. Un total de 10.5 secondes s'est écoulé pour les deux ensemble sur un Core Duo à 2,4 GHz. 25 gigacycles = un peu plus de 10 secondes à cette fréquence d'horloge, je suis donc convaincu que mon timing est correct.

Cela concerne les chaînes de dépendance d’instruction, qui sont très mauvaises pour cet algorithme. Je pouvais presque doubler à nouveau la vitesse en utilisant une paire de registres 64 bits. En fait, si j’étais intelligent et que j’ajoutais x + y un peu plus tôt, je pouvais me débarrasser de certains quarts de travail. La version 64 bits, avec quelques petites modifications, sortirait à peu près égale, mais compterait encore deux fois plus de bits.

Avec les registres SIMD 128 bits, c’est encore un facteur deux, et les jeux d’instructions SSE comportent souvent des raccourcis intelligents.

Il n'y a aucune raison pour que le code soit particulièrement transparent. L'interface est simple, l'algorithme peut être référencé en ligne à de nombreux endroits et se prête à un test unitaire complet. Le programmeur qui tombe dessus peut même apprendre quelque chose. Ces opérations de bits sont extrêmement naturelles au niveau de la machine.

OK, j’ai décidé de miser sur la version 64 bits modifiée. Pour ce one 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;
}

Cela semble à peu près correct (je ne teste pas soigneusement, cependant). Maintenant, les timings sortent à 10,70 gigacycles / 14,1 gigacycles. Ce dernier chiffre a totalisé 128 milliards de bits et correspond à 5,9 secondes écoulées sur cette machine. La version non parallèle accélère un peu, car je suis en mode 64 bits et aime les registres 64 bits légèrement supérieurs aux registres 32 bits.

Voyons s'il y a un peu plus de OOO pipelins ici. C’était un peu plus compliqué, alors j’ai testé un peu. Chaque terme représente à lui seul 64, la somme totale étant égale à 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;
}

J'étais excité pendant un moment, mais il s'avère que gcc joue des tours inline avec -O3 même si je n'utilise pas le mot clé inline dans certains tests. Lorsque je laisse gcc jouer à des tours, un milliard d'appels à pop4 () prennent 12,56 gigacycles, mais j'ai déterminé qu'il s'agissait de plier des arguments en tant qu'expressions constantes. Un nombre plus réaliste semble être 19.6gc pour une autre accélération de 30%. Ma boucle de test ressemble maintenant à ceci: assurez-vous que chaque argument est suffisamment différent pour empêcher gcc de jouer des tours.

   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 milliards de bits additionnés sur 8.17 secondes écoulées. Fonctionne à 1.02s pour 32 millions de bits, comme indiqué dans la recherche de tableau 16 bits. Impossible de comparer directement, car l’autre banc ne donne pas une vitesse d’horloge, mais on dirait que j’ai tiré la morve de l’édition de table de 64 Ko, ce qui est une utilisation tragique du cache L1 en premier lieu.

Mise à jour: a décidé de procéder à l'évidence et de créer pop6 () en ajoutant quatre lignes dupliquées supplémentaires. Sorti à 22,8gc, 384 milliards de bits additionnés en 9,5s se sont écoulés. Donc, il y a encore 20% Maintenant à 800 ms pour 32 milliards de bits.

Pourquoi ne pas diviser de manière itérative par 2?

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

Je conviens que ce n'est pas le plus rapide, mais & "meilleur &"; est un peu ambigu. Je dirais cependant que & "Meilleur &"; devrait avoir un élément de clarté

Le piratage de bits du délice du pirate devient tellement plus clair lorsque vous écrivez les modèles de bits.

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

La première étape ajoute les bits pairs aux bits impairs, produisant une somme de bits dans chaque deux. Les autres étapes ajoutent des morceaux d’ordre élevé aux ordres d’ordre faible, en doublant la taille de l’ensemble, jusqu’à ce que le décompte final prenne tout l’int.

Pour un juste milieu entre une table de consultation 2 32 et une itération individuelle dans chaque 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;
}

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

Ce n’est pas la solution la plus rapide ni la meilleure, mais j’ai trouvé la même question et j’ai commencé à réfléchir. Enfin, j'ai réalisé que cela peut se faire de la sorte si le problème est mathématique et que vous tracez un graphique, alors vous trouvez que c'est une fonction qui a une partie périodique, puis vous vous rendez compte de la différence entre les périodes ... alors voilà:

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

Cela peut être fait dans O(k), où k est le nombre de bits définis.

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

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

    return count;
}

La fonction que vous recherchez est souvent appelée & "somme latérale &"; ou " dénombrement de la population " d'un nombre binaire. Knuth en parle dans le pré-fascicule 1A, pages 11-12 (bien qu’il y ait une brève référence dans le Volume 2, 4.6.3- (7).)

Le locus classicus est l'article de Peter Wegner & "Une technique pour compter les éléments dans un ordinateur binaire &"; extrait du Communications de l'ACM , Volume 3 (1960) numéro 5, page 322 . Il y donne deux algorithmes différents, l'un optimisé pour les nombres censés être & "; Sparse &"; (c’est-à-dire un petit nombre d’entre eux) et un pour le cas contraire.

Quelques questions ouvertes: -

  1. Si le nombre est négatif, alors?
  2. Si le nombre est 1024, la division & divise de manière itérative par 2 & "; Cette méthode itérera 10 fois.

nous pouvons modifier l'algo pour supporter le nombre négatif comme suit: -

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

maintenant pour surmonter le deuxième problème, nous pouvons écrire l’algo comme ceci: -

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

pour une référence complète, voir:

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

Je pense que la méthode de sera également utile ... Il parcourt autant d'itérations qu'il y a de bits définis. Donc, si nous avons un mot de 32 bits avec uniquement le bit de poids fort, il ne passera qu'une fois dans la boucle.

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

Publié en 1988, le langage de programmation C 2e éd. (par Brian W. Kernighan et Dennis M. Ritchie) le mentionne dans l’exercice 2-9. Le 19 avril 2006, Don Knuth m’a fait remarquer que cette méthode avait été publiée pour la première fois par Peter Wegner dans le CACM 3 (1960), p. 322. (Découvert également de façon indépendante par Derrick Lehmer et publié en 1964 dans un livre publié par Beckenbach.) & ";

J'utilise le code ci-dessous, qui est plus intuitif.

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

Logic: n & amp; (n-1) réinitialise le dernier bit défini sur n.

P.S: Je sais que ce n’est pas une solution O (1), mais une solution intéressante.

Que voulez-vous dire par & "Meilleur algorithme &"? Le code raccourci ou le code à jeun? Votre code est très élégant et le temps d'exécution est constant. Le code est également très court.

Mais si la vitesse est le facteur principal et non la taille du code, je pense que le suivi peut être plus rapide:

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

Je pense que cela ne sera pas plus rapide pour une valeur de 64 bits, mais une valeur de 32 bits peut être plus rapide.

J'ai écrit une macro de calcul de nombre de bits rapide pour les machines RISC vers 1990. Elle n'utilise pas l'arithmétique avancée (multiplication, division,%), les extractions de mémoire (beaucoup trop lentes), les branches (beaucoup trop lentes), mais elle suppose Le processeur a un décaleur de baril de 32 bits (en d’autres termes, & Gt; & Gt; 1 et & Gt; & Gt; 32 prennent le même nombre de cycles). Il suppose que de petites constantes ( tels que 6, 12, 24) ne coûtent rien à charger dans les registres, ou sont stockés dans des temporaires et réutilisés encore et encore.

Avec ces hypothèses, il compte 32 bits en environ 16 cycles / instructions sur la plupart des machines RISC. Notez que 15 instructions / cycles est proche d'une limite inférieure du nombre de cycles ou d'instructions, car il semble prendre au moins 3 instructions (masque, décalage, opérateur) pour réduire de moitié le nombre d'addend, donc log_2 (32). = 5, 5 x 3 = 15 instructions est une quasi-limite inférieure.

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

Voici un secret pour la première et la plus complexe des étapes:

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

donc si je prends la 1ère colonne (A) ci-dessus, le décale d’un bit à droite et le soustrait de AB, j’obtiens la sortie (CD). L'extension à 3 bits est similaire; vous pouvez le vérifier avec une table booléenne à 8 lignes comme la mienne ci-dessus si vous le souhaitez.

  • Don Gillies

si vous utilisez C ++, une autre option consiste à utiliser la métaprogrammation des modèles:

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

utilisation serait:

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

vous pouvez bien sûr développer davantage ce modèle pour utiliser différents types (même la taille de bit à détection automatique), mais je l’ai simplifié pour plus de clarté.

edit: j'ai oublié de mentionner que c'est bien parce que cela devrait fonctionner avec n'importe quel compilateur C ++ et qu'il ne fait que dérouler votre boucle si une valeur constante est utilisée pour le nombre de bits (en d'autres termes, je suis sûr que c'est la méthode générale la plus rapide que vous trouverez)

J'aime particulièrement cet exemple du fichier fortune:

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

J'aime mieux parce que c'est tellement joli!

Java JDK1.5

Integer.bitCount (n);

où n est le nombre dont les 1 doivent être comptés.

vérifiez également,

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

J'ai trouvé une implémentation du comptage de bits dans un tableau avec l'utilisation de l'instruction SIMD (SSSE3 et AVX2). Ses performances sont 2 à 2,5 fois meilleures que si elle utilisait la fonction intrinsèque __popcnt64.

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

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

J'utilise toujours cela dans la programmation compétitive et il est facile à écrire et efficace:

#include <bits/stdc++.h>

using namespace std;

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

Il existe de nombreux algorithmes pour compter les bits définis; mais je pense que le meilleur est le plus rapide! Vous pouvez voir les détails sur cette page:

Des bribes de bidouilles

Je suggère celui-ci:

Comptage des bits définis dans des mots de 14, 24 ou 32 bits à l'aide d'instructions 64 bits

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;

Cette méthode nécessite un processeur 64 bits à division de module rapide pour être efficace. La première option ne prend que 3 opérations; la deuxième option prend 10; et la troisième option prend 15.

Solution rapide en C # utilisant un tableau précalculé de comptes de bits en octets avec branchement sur la taille de l'entré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
    };
}

Voici un module portable (ANSI-C) qui peut analyser chacun de vos algorithmes sur n’importe quelle architecture.

Votre CPU a 9 octets bits? Pas de problème :-) Pour le moment, il implémente 2 algorithmes, l'algorithme K & Et R et une table de recherche par octets. La table de correspondance est en moyenne 3 fois plus rapide que l’algorithme K &. Si quelqu'un peut trouver un moyen de tirer le & Quot; Hacker's Delight & Quot; algorithme portable, n'hésitez pas à l'ajouter.

#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 bits ou pas? Je viens d'arriver avec cette méthode en Java après avoir lu & "; / a> " 4ème édition exercice 5.5 (chapitre 5: Manipulation de bits). Si le bit le moins significatif est égal à 1 incrément count, déplacez le nombre entier vers la droite.

public static int bitCount( int n){
    int count = 0;
    for (int i=n; i!=0; i = i >> 1){
        count += i & 1;
    }
    return count;
}

Je pense que celle-ci est plus intuitive que les solutions à constante 0x33333333, quelle que soit leur rapidité. Cela dépend de votre définition de & Quot; meilleur algorithme & Quot; .

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top