8 bits représentant le nombre 7 ressemblent à ceci:
00000111
Trois bits sont définis.
Quels sont les algorithmes pour déterminer le nombre de bits définis dans un entier 32 bits?
8 bits représentant le nombre 7 ressemblent à ceci:
00000111
Trois bits sont définis.
Quels sont les algorithmes pour déterminer le nombre de bits définis dans un entier 32 bits?
Réponses:
Ceci est connu sous le nom de « poids Hamming », «popcount» ou «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 les vecteurs de bits. Les instructions parallèles (comme les x86 popcnt
, sur les processeurs où il est pris en charge) seront presque 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 grand cache et / ou si vous exécutez beaucoup de ces instructions dans une boucle serrée. Cependant, il peut souffrir à cause du coût d'un «échec de cache», où le CPU doit récupérer une partie de la table de la mémoire principale. (Recherchez chaque octet séparément pour garder la table petite.)
Si vous savez que vos octets seront principalement des 0 ou des 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, connu sous le nom d'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 qu'il fonctionne pour un langage particulier (par exemple en utilisant uint32_t pour C ++ et >>> en Java):
int numberOfSetBits(uint32_t i)
{
// Java: use int, and use >>> instead of >>
// C or C++: use uint32_t
i = i - ((i >> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >> 2) & 0x33333333);
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}
Pour JavaScript: contraindre à un entier avec |0
pour des performances: changez la première ligne eni = (i|0) - ((i >> 1) & 0x55555555);
Cela a le meilleur comportement dans le pire des cas de tous les algorithmes discutés, donc traitera efficacement tout modèle d'utilisation ou les valeurs que vous lui lancerez.
i = i - ((i >> 1) & 0x55555555);
La première étape est une version optimisée du masquage pour isoler les bits pairs / impairs, les décaler pour les aligner et les ajouter. Cela fait effectivement 16 ajouts distincts dans des accumulateurs 2 bits ( SWAR = SIMD dans un registre ). Comme (i & 0x55555555) + ((i>>1) & 0x55555555)
.
L'étape suivante prend les huit paires paires / impaires de ces 16x accumulateurs 2 bits et les ajoute à nouveau, produisant des sommes 8x 4 bits. L' i - ...
optimisation n'est pas possible cette fois-ci, elle masque donc juste avant / après le décalage. L'utilisation de la même 0x33...
constante les deux fois plutôt 0xccc...
qu'avant le décalage est une bonne chose lors de la compilation pour les ISA qui doivent construire des constantes 32 bits dans des registres séparément.
La dernière étape de changement et d'ajout de (i + (i >> 4)) & 0x0F0F0F0F
s'élargit à 4 accumulateurs 8 bits. Il masque après l' ajout au lieu d'avant, car la valeur maximale dans tout accumulateur à 4 bits est 4
, si les 4 bits des bits d'entrée correspondants ont été définis. 4 + 4 = 8 qui tient toujours sur 4 bits, donc le transfert entre les éléments de quartet est impossible dans i + (i >> 4)
.
Jusqu'à présent, il s'agit simplement d'une carte SIMD assez normale utilisant des techniques SWAR avec quelques optimisations intelligentes. Continuer avec le même modèle pour 2 étapes supplémentaires peut s'étendre à 2 x 16 bits puis 1 x 32 bits. Mais il existe un moyen plus efficace sur les machines à multiplication matérielle rapide:
Une fois que nous avons assez "d'éléments", une multiplication avec une constante magique peut additionner tous les éléments dans l'élément supérieur . Dans ce cas, les éléments d'octet. La multiplication se fait par décalage vers la gauche et addition, donc une multiplication des x * 0x01010101
résultats x + (x<<8) + (x<<16) + (x<<24)
. Nos éléments 8 bits sont suffisamment larges (et contiennent des nombres suffisamment petits) pour que cela ne produise pas de report dans les 8 bits supérieurs.
Une version 64 bits de ceci peut faire 8x éléments 8 bits dans un entier 64 bits avec un multiplicateur 0x010101010101010101 et extraire l'octet haut avec >>56
. Il ne prend donc pas d'étapes supplémentaires, juste des constantes plus larges. C'est ce que GCC utilise __builtin_popcountll
sur les systèmes x86 lorsque l' popcnt
instruction matérielle n'est pas activée. Si vous pouvez utiliser des fonctions intégrées ou intrinsèques à cette fin, faites-le pour donner au compilateur la possibilité d'effectuer des optimisations spécifiques à la cible.
Cet algorithme bit à bit-SWAR pourrait se paralléliser pour être fait dans plusieurs éléments vectoriels à la fois, plutôt que dans un seul registre entier, pour une accélération sur les CPU avec SIMD mais sans instruction de popcount utilisable. (par exemple, le code x86-64 qui doit s'exécuter sur n'importe quel processeur, pas seulement Nehalem ou version ultérieure.)
Cependant, la meilleure façon d'utiliser les instructions vectorielles pour popcount est généralement d'utiliser un shuffle variable pour effectuer une recherche de table sur 4 bits à la fois de chaque octet en parallèle. (Les 4 bits indexent une table à 16 entrées contenue dans un registre vectoriel).
Sur les processeurs Intel, l'instruction popcnt matérielle 64 bits peut surpasser une implémentation parallèle-bit SSSE3PSHUFB
d'environ un facteur 2, mais uniquement si votre compilateur l'obtient parfaitement . Sinon, l'ESS peut sortir nettement en tête. Les versions de compilateur plus récentes sont conscientes du problème de fausse dépendance popcnt sur Intel .
Références:
unsigned int
pour montrer facilement qu'il est exempt de toute complication de bit de signe. Serait uint32_t
également plus sûr, comme dans, vous obtenez ce que vous attendez sur toutes les plateformes?
>>
est défini par l'implémentation pour les valeurs négatives. L'argument doit être modifié (ou converti) en unsigned
, et puisque le code est spécifique à 32 bits, il devrait probablement être utilisé uint32_t
.
Tenez également compte des 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 intrinsèques GCC fonctionnent même sur plusieurs plates-formes. Popcount deviendra courant 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 popcnt
charge des instructions avec -mpopcnt
ou -msse4.2
pour activer également les instructions vectorielles ajoutées dans la même génération. Voir les options de GCC x86 . -march=nehalem
(ou -march=
quel que soit le processeur que vous voulez que votre code assume et ajuste) pourrait être un bon choix. L'exécution du binaire résultant sur un processeur plus ancien entraînera une erreur d'instruction illégale.
Pour rendre les binaires optimisés pour la machine sur laquelle vous les construisez, utilisez -march=native
(avec gcc, clang ou ICC).
MSVC fournit un intrinsèque pour l' popcnt
instruction x86 , mais contrairement à gcc, c'est vraiment un intrinsèque pour l'instruction matérielle et nécessite un support matériel.
Utilisation std::bitset<>::count()
au lieu d'un intégré
En théorie, tout compilateur qui sait comment effectuer un décompte efficace pour le processeur cible doit exposer cette fonctionnalité via ISO C ++ std::bitset<>
. En pratique, vous pourriez être mieux avec le bit-hack ET / shift / ADD dans certains cas pour certains CPU cibles.
Pour les architectures cibles où le popcount matériel est une extension facultative (comme x86), tous les compilateurs n'en ont pas qui en tirent std::bitset
parti lorsqu'ils sont disponibles. Par exemple, MSVC n'a aucun moyen d'activer la popcnt
prise en charge au moment de la compilation et utilise toujours une recherche de table , même avec /Ox /arch:AVX
(ce qui implique SSE4.2, bien que techniquement il y ait un bit de fonctionnalité distinct pour popcnt
.)
Mais au moins, vous obtenez quelque chose de portable qui fonctionne partout, et avec gcc / clang avec les bonnes options cibles, vous obtenez un popcount 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 asm de gcc, clang, icc et MSVC sur l'explorateur du compilateur Godbolt.
x86-64 gcc -O3 -std=gnu++11 -mpopcnt
émet ceci:
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
émet (pour la int
version arg):
rldicl 3,3,0,32 # zero-extend from 32 to 64-bit
popcntd 3,3 # popcount
blr
Cette source n'est pas spécifique à x86 ou spécifique à GNU, mais se compile bien uniquement pour x86 avec gcc / clang / icc.
Notez également que le remplacement de gcc pour les architectures sans popcount à instruction unique est une recherche de table octet par octet. Ce n'est pas merveilleux pour ARM, par exemple .
std::bitset::count
. après avoir inséré cela compile en un seul __builtin_popcount
appel.
À mon avis, la "meilleure" solution est celle qui peut être lue par un autre programmeur (ou le programmeur d'origine deux ans plus tard) sans commentaires abondants. Vous voudrez peut-être la solution la plus rapide ou la plus intelligente que certains aient déjà fournie, mais je préfère la lisibilité à l'intelligence à 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 vitesse (et en supposant que vous le documentiez bien pour aider vos successeurs), vous pouvez utiliser une recherche de table:
// 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 dépendent de tailles de types de données spécifiques, ils ne sont donc pas portables. Mais, comme de nombreuses optimisations de performances ne sont pas portables de toute façon, cela peut ne pas être un problème. Si vous voulez la portabilité, je m'en tiendrai à la solution lisible.
if ((value & 1) == 1) { count++; }
par count += value & 1
?
Extrait de Hacker's Delight, 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 instructions de ~ 20-ish (dépend de l'arc), pas de branchement.
Hacker's Delight est délicieux! Hautement recommandé.
Integer.bitCount(int)
utilise cette même implémentation exacte.
pop
au lieu de population_count
(ou pop_cnt
si vous devez avoir une abréviation). @MarcoBolis Je suppose que cela sera vrai pour toutes les versions de Java, mais officiellement cela dépendra de l'implémentation :)
Je pense que le moyen le plus rapide - sans utiliser de tables de recherche et de popcount - 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 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 sur deux bits peut être 0b00
, 0b01
ou 0b10
. Essayons de travailler cela sur 2 bits.
---------------------------------------------
| v | (v >> 1) & 0b0101 | v - x |
---------------------------------------------
0b00 0b00 0b00
0b01 0b00 0b01
0b10 0b01 0b01
0b11 0b01 0b10
C'est ce qui était requis: la dernière colonne indique le nombre de bits définis dans chaque paire de deux bits. Si le nombre à deux bits est >= 2 (0b10)
alors and
produit 0b01
, sinon il produit 0b00
.
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 dans tous les deux bits, maintenant nous résumons ce nombre dans 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 affirmation est la plus délicate.
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
Décomposons-le plus loin ...
v + (v >> 4)
C'est similaire à la deuxième déclaration; nous comptons plutôt les bits définis par groupes de 4. Nous savons - en raison de nos opérations précédentes - que chaque quartet contient le nombre de bits définis. Regardons un exemple. Supposons que nous ayons l'octet 0b01000010
. Cela signifie que le premier quartet a son jeu de 4 bits et le second a son jeu de 2 bits. Maintenant, nous ajoutons ces grignotages ensemble.
0b01000010 + 0b01000000
Il nous donne le nombre de bits définis dans un octet, dans le premier quartet 0b01100010
et donc nous masquons les quatre derniers octets de tous les octets du nombre (en les éliminant).
0b01100010 & 0xF0 = 0b01100000
Désormais, chaque octet contient le nombre de bits définis. Nous devons les additionner tous ensemble. L'astuce consiste à multiplier le résultat par 0b10101010
lequel a une propriété intéressante. Si notre numéro a quatre octets, A B C D
il en résultera un nouveau numéro avec ces octets A+B+C+D B+C+D C+D D
. Un nombre de 4 octets peut avoir un maximum de 32 bits, qui peuvent être représentés comme 0b00100000
.
Tout ce dont nous avons besoin maintenant est le premier octet qui a la somme de tous les bits définis dans tous les octets, et nous l'obtenons >> 24
. Cet algorithme a été conçu pour les 32 bit
mots mais peut être facilement modifié pour les 64 bit
mots.
c =
il? On dirait qu'il devrait être éliminé. De plus, suggérez un jeu de paren supplémentaire A "((((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24" pour éviter certains avertissements classiques.
popcount(int v)
et popcount(unsigned v)
. Pour la portabilité, considérez popcount(uint32_t v)
, etc. Vraiment comme la partie * 0x1010101.
return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
nous n'avons donc pas besoin de compter les lettres pour voir ce que vous faites réellement (puisque vous avez supprimé la première 0
, j'ai accidentellement pensé que vous aviez utilisé le mauvais motif de bits (inversé) comme masque - c'est jusqu'à ce que je note qu'il n'y a que 7 lettres et non 8).
Je me suis ennuyé et j'ai chronométré un milliard d'itérations de trois approches. Le compilateur est gcc -O3. Le CPU est tout ce qu'ils mettent dans le Macbook Pro de 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 un peu de temps, qui a pris 8,6 secondes.
La quatrième place revient à __builtin_popcount () de GCC, à une honteuse 11 secondes.
L'approche de comptage un bit à la fois a été plus lente, et je me suis ennuyé d'attendre qu'elle se termine.
Donc, si vous vous souciez de la performance avant tout, utilisez la première approche. Si vous vous en souciez, mais pas assez pour y dépenser 64 Ko de RAM, utilisez la deuxième approche. Sinon, utilisez l'approche un bit à la fois lisible (mais lente).
Il est difficile de penser à une situation dans laquelle vous voudriez utiliser l'approche du bit-twiddling.
Edit: Résultats similaires ici .
S'il vous arrive d'utiliser Java, la méthode intégrée le Integer.bitCount
fera.
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;
}
Permettez-moi d'expliquer cet algorithme.
Cet algorithme est basé sur l'algorithme Divide and Conquer. Supposons qu'il existe un entier 8 bits 213 (11010101 en binaire), l'algorithme fonctionne comme ceci (à chaque fois 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 sous gcc 4.3.3 compilées avec -O3 en utilisant les lignes C ++ pour éliminer la surcharge des appels de fonction, un milliard d'itérations, en gardant la somme cumulée de tous les décomptes pour garantir que le compilateur ne supprime rien d'important, en utilisant rdtsc pour le timing ( cycle d'horloge précis).
inline int pop2 (non signé x, non signé 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); retourner (x + y) & 0x000000FF; }
Le Hacker's Delight non modifié a pris 12,2 gigacycles. Ma version parallèle (comptant deux fois plus de bits) fonctionne en 13,0 gigacycles. 10,5 s au total se sont écoulés pour les deux ensemble sur un Core Duo à 2,4 GHz. 25 gigacycles = un peu plus de 10 secondes à cette fréquence d'horloge, donc je suis sûr que mes horaires sont corrects.
Cela a à voir avec les chaînes de dépendance des instructions, qui sont très mauvaises pour cet algorithme. Je pourrais presque doubler à nouveau la vitesse en utilisant une paire de registres 64 bits. En fait, si j'étais intelligent et ajoutais x + ya un peu plus tôt, je pourrais raser certains changements. La version 64 bits avec quelques petits ajustements serait à peu près égale, mais compterait à nouveau deux fois plus de bits.
Avec les registres SIMD 128 bits, encore un autre facteur de deux, et les jeux d'instructions SSE ont souvent aussi 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 il se prête à un test unitaire complet. Le programmeur qui tombe dessus pourrait 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 mettre la version 64 bits modifiée au banc. Pour cette taille unique (long non signé) == 8
inline int pop2 (unsigned long x, unsigned long y) { x = x - ((x >> 1) et 0x5555555555555555); y = y - ((y >> 1) & 0x5555555555555555); x = (x & 0x333333333333333333) + ((x >> 2) & 0x3333333333333333); y = (y & 0x333333333333333333) + ((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); retourner x & 0xFF; }
Cela semble correct (je ne teste pas soigneusement, cependant). Maintenant, les timings sortent à 10,70 gigacycles / 14,1 gigacycles. Ce nombre ultérieur totalisait 128 milliards de bits et correspond à 5,9 secondes écoulées sur cette machine. La version non parallèle accélère un tout petit peu car je suis en mode 64 bits et elle aime les registres 64 bits légèrement mieux que les registres 32 bits.
Voyons voir s'il y a un peu plus de pipelines OOO à avoir ici. C'était un peu plus compliqué, donc j'ai testé un peu. Chaque terme totalise à lui seul 64, la somme combinée à 256.
inline int pop4 (unsigned long x, unsigned long y, non signé long u, non signé long v) { enum {m1 = 0x555555555555555555, 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); retourner x & 0x000001FF; }
J'étais excité pendant un moment, mais il s'avère que gcc joue des tours en ligne avec -O3 même si je n'utilise pas le mot-clé en ligne dans certains tests. Quand j'ai laissé gcc jouer des tours, un milliard d'appels à pop4 () prend 12,56 gigacycles, mais j'ai déterminé qu'il pliait les arguments comme des expressions constantes. Un nombre plus réaliste semble être de 19,6 gc pour une autre accélération de 30%. Ma boucle de test ressemble maintenant à ceci, en m'assurant que chaque argument est suffisamment différent pour empêcher gcc de jouer des tours.
hitime b4 = rdtsc (); pour (non signé long i = 10L * 1000 * 1000 * 1000; i <11L * 1000 * 1000 * 1000; ++ i) somme + = pop4 (i, i ^ 1, ~ i, i | 1); hitime e4 = rdtsc ();
256 milliards de bits additionnés en 8,17 secondes se sont écoulés. Fonctionne à 1,02 s pour 32 millions de bits comme indiqué dans la recherche de table 16 bits. Je ne peux pas comparer directement, car l'autre banc ne donne pas de vitesse d'horloge, mais il semble que j'ai supprimé la morve de l'édition de table de 64 Ko, ce qui est une utilisation tragique du cache L1 en premier lieu.
Mise à jour: décidé de faire l'évidence et de créer pop6 () en ajoutant quatre autres lignes dupliquées. Entré à 22,8 gc, 384 milliards de bits additionnés en 9,5 secondes se sont écoulés. Il y a donc encore 20% à 800 ms pour 32 milliards de bits.
Pourquoi ne pas diviser itérativement par 2?
count = 0 tandis que n> 0 si (n% 2) == 1 compter + = 1 n / = 2
Je suis d'accord que ce n'est pas le plus rapide, mais "le meilleur" est quelque peu ambigu. Je dirais cependant que "le meilleur" devrait avoir un élément de clarté
Le bit-twiddling de Hacker's Delight devient tellement plus clair lorsque vous écrivez les motifs 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 chacun d'eux. Les autres étapes ajoutent des morceaux d'ordre élevé aux morceaux d'ordre inférieur, doublant la taille du morceau jusqu'à ce que le décompte final prenne l'intégralité de l'intégralité.
Pour un juste milieu entre une table de recherche 2 32 et une itération à travers chaque bit individuellement:
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;
}
Cela peut être fait dans O(k)
, où k
est le nombre de bits défini.
int NumberOfSetBits(int n)
{
int count = 0;
while (n){
++ count;
n = (n - 1) & n;
}
return count;
}
n &= (n-1)
forme la plus succincte .
Ce n'est pas la solution la plus rapide ou la meilleure, mais j'ai trouvé la même question à ma manière, et j'ai commencé à réfléchir et à réfléchir. enfin j'ai réalisé que cela peut être fait comme ça si vous obtenez le problème du côté mathématique, et dessinez un graphique, alors vous trouvez que c'est une fonction qui a une partie périodique, puis vous réalisez la différence entre les périodes ... donc Voici:
unsigned int f(unsigned int x)
{
switch (x) {
case 0:
return 0;
case 1:
return 1;
case 2:
return 1;
case 3:
return 2;
default:
return f(x/4) + f(x%4);
}
}
def f(i, d={0:lambda:0, 1:lambda:1, 2:lambda:1, 3:lambda:2}): return d.get(i, lambda: f(i//4) + f(i%4))()
La fonction que vous recherchez est souvent appelée «somme latérale» ou «nombre de population» d'un nombre binaire. Knuth en parle dans le pré-fascicule 1A, pp11-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 "A Technique for Counting Ones in a Binary Computer", tiré des 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 "clairsemés" (c'est-à-dire qu'ils en ont un petit nombre) et l'autre pour le cas contraire.
Quelques questions ouvertes: -
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: -
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:
Je pense que la méthode de Brian Kernighan sera également utile ... Elle passe par 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 le plus élevé, il ne passera qu'une seule 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 C Programming Language 2nd Ed. (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 "a été publiée pour la première fois par Peter Wegner dans CACM 3 (1960), 322. (Également découverte indépendamment par Derrick Lehmer et publiée en 1964 dans un livre édité par Beckenbach.)"
J'utilise le code ci-dessous qui est plus intuitif.
int countSetBits(int n) {
return !n ? 0 : 1 + countSetBits(n & (n-1));
}
Logique: n & (n-1) réinitialise le dernier bit défini de n.
PS: Je sais que ce n'est pas une solution O (1), quoique intéressante.
O(ONE-BITS)
. Il s'agit bien de O (1) car il y a au plus 32 bits à un.
Que voulez-vous dire par "meilleur algorithme"? Le code court ou le code jeûné? Votre code a l'air très élégant et il a un temps d'exécution constant. Le code est également très court.
Mais si la vitesse est le facteur majeur et non la taille du code, je pense que la suite 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 ce ne sera pas plus rapide pour une valeur 64 bits mais une valeur 32 bits peut être plus rapide.
J'ai écrit une macro de comptage de bits rapide pour les machines RISC vers 1990. Elle n'utilise pas d'arithmétique avancée (multiplication, division,%), de récupération de mémoire (beaucoup trop lente), de branches (trop lente), mais elle suppose que le CPU a un Décalage en barillet 32 bits (en d'autres termes, >> 1 et >> 32 prennent le même nombre de cycles.) Il suppose que les petites constantes (telles que 6, 12, 24) ne coûtent rien à charger dans les registres, ou sont stockées dans les temporaires et réutilisé 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 sur le 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'addends, donc log_2 (32) = 5, 5 x 3 = 15 instructions est une limite quasi-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 étape la plus complexe:
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, la décale de 1 bit vers la droite et la soustrais de AB, j'obtiens la sortie (CD). L'extension à 3 bits est similaire; vous pouvez le vérifier avec une table booléenne à 8 rangées comme la mienne ci-dessus si vous le souhaitez.
si vous utilisez C ++, une autre option consiste à utiliser la métaprogrammation de modèle:
// recursive template to sum bits in an int
template <int BITS>
int countBits(int val) {
// return the least significant bit plus the result of calling ourselves with
// .. the shifted value
return (val & 0x1) + countBits<BITS-1>(val >> 1);
}
// template specialisation to terminate the recursion when there's only one bit left
template<>
int countBits<1>(int val) {
return val & 0x1;
}
l'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 étendre davantage ce modèle pour utiliser différents types (même la taille de bits à détection automatique) mais je l'ai gardé simple pour plus de clarté.
edit: oublié de mentionner que c'est bon car cela devrait fonctionner dans n'importe quel compilateur C ++ et il déroule simplement votre boucle pour vous si une valeur constante est utilisée pour le nombre de bits (en d'autres termes, je suis presque sûr que c'est la méthode générale la plus rapide tu trouveras)
constexpr
cependant.
J'aime particulièrement cet exemple du fichier de fortune:
#define BITCOUNT (x) (((BX_ (x) + (BX_ (x) >> 4)) & 0x0F0F0F0F)% 255) #define BX_ (x) ((x) - (((x) >> 1) & 0x77777777) - (((x) >> 2) & 0x33333333) - (((x) >> 3) & 0x11111111))
Je l'aime mieux parce que c'est si joli!
Java JDK1.5
Integer.bitCount (n);
où n est le nombre dont les 1 doivent être comptés.
vérifiez aussi,
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 supérieures à celles de 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];
}
Il existe de nombreux algorithmes pour compter les bits définis; mais je pense que le meilleur est le plus rapide! Vous pouvez voir le détail sur cette page:
Je suggère celui-ci:
Comptage des bits définis en 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 avec une 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 C # rapide utilisant un tableau pré-calculé de décomptes d'octets avec branchement sur la taille d'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
};
}
(0xe994 >>(k*2))&3
, sans accès à la mémoire ...
Voici un module portable (ANSI-C) qui peut comparer chacun de vos algorithmes sur n'importe quelle architecture.
Votre CPU a 9 octets de bits? Pas de problème :-) Pour le moment, il implémente 2 algorithmes, l'algorithme K&R et une table de recherche par octets. La table de recherche est en moyenne 3 fois plus rapide que l'algorithme K&R. Si quelqu'un peut trouver un moyen de rendre portable l'algorithme "Hacker's Delight", 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
ce que tu peux faire c'est
while(n){
n=n&(n-1);
count++;
}
la logique derrière cela est que les bits de n-1 sont inversés par rapport au bit le plus à droite de n. si n = 6, c'est-à-dire 110, alors 5 est 101, les bits sont inversés par rapport au bit le plus à droite de n. Donc, si nous et ces deux, nous ferons le bit le plus à droite 0 à chaque itération et irons toujours au bit défini le plus à droite suivant, d'où le comptage du bit défini.