Quel est le moyen le plus rapide de retourner les positions de tous les bits définis dans un entier 64 bits?

J’ai besoin d’un moyen rapide pour obtenir la position de tous les bits dans un entier de 64 bits. Par exemple, étant donné x = 123703 , j’aimerais remplir un tableau idx[] = {0, 1, 2, 4, 5, 8, 9, 13, 14, 15, 16} . Nous pouvons supposer que nous connaissons le nombre de bits a priori. On appellera cela 10 ^ 12 – 10 ^ 15 fois, donc la vitesse est essentielle. La réponse la plus rapide que j’ai trouvée jusqu’à présent est la monstruosité suivante, qui utilise chaque octet de l’entier 64 bits comme index dans les tables qui indiquent le nombre de bits défini dans cet octet et les positions de ceux-ci:

 int64_t x; // this is the input unsigned char idx[K]; // this is the array of K bits that are set unsigned char *dst=idx, *src; unsigned char zero, one, two, three, four, five; // these hold the 0th-5th bytes zero = x & 0x0000000000FFUL; one = (x & 0x00000000FF00UL) >> 8; two = (x & 0x000000FF0000UL) >> 16; three = (x & 0x0000FF000000UL) >> 24; four = (x & 0x00FF00000000UL) >> 32; five = (x & 0xFF0000000000UL) >> 40; src=tab0+tabofs[zero ]; COPY(dst, src, n[zero ]); src=tab1+tabofs[one ]; COPY(dst, src, n[one ]); src=tab2+tabofs[two ]; COPY(dst, src, n[two ]); src=tab3+tabofs[three]; COPY(dst, src, n[three]); src=tab4+tabofs[four ]; COPY(dst, src, n[four ]); src=tab5+tabofs[five ]; COPY(dst, src, n[five ]); 

COPY est une instruction switch permettant de copier jusqu’à 8 octets, n est un tableau du nombre de bits défini dans un octet et les tabofs donnent le décalage dans tabX , qui contient les positions des bits définis dans le X-ième octet. Ceci est environ 3 fois plus rapide que les méthodes basées sur les boucles déroulées avec __builtin_ctz() sur mon Xeon E5-2609. (Voir ci-dessous.) Je répète actuellement x dans l’ordre lexicographique pour un nombre donné de bits défini.

Y a-t-il une meilleure façon?

EDIT : Ajout d’un exemple (que j’ai par la suite corrigé). Le code complet est disponible ici: http://pastebin.com/79X8XL2P . Note: GCC avec -O2 semble l’optimiser, mais le compilateur Intel (que j’ai utilisé pour le composer) ne …

Permettez-moi également de donner quelques informations supplémentaires pour répondre à certains des commentaires ci-dessous. Le but est d’effectuer un test statistique sur chaque sous-ensemble possible de variables K dans un univers de N variables explicatives possibles; la cible spécifique est maintenant N = 41, mais je peux voir certains projets nécessitant N jusqu’à 45-50. Le test consiste essentiellement à factoriser la sous-masortingce de données correspondante. En pseudocode, quelque chose comme ceci:

 double doTest(double *data, int64_t model) { int nidx, idx[]; double submasortingx[][]; nidx = getIndices(model, idx); // get the locations of ones in model // copy data into submasortingx for(int i=0; i<nidx; i++) { for(int j=0; j<nidx; j++) { submatrix[i][j] = data[idx[i]][idx[j]]; } } factorize(submatrix, nidx); return the_answer; } 

J’en ai codé une version pour une carte Intel Phi qui devrait compléter le cas N = 41 en 15 jours environ, dont ~ 5-10% du temps est passé dans un getIndices() naïf, donc dès que possible la version pourrait sauver un jour ou plus. Je travaille également sur une implémentation pour NVidia Kepler, mais malheureusement, le problème que je rencontre (nombre dérisoire de petites opérations masortingcielles) n’est pas parfaitement adapté au matériel (opérations masortingcielles ridiculement volumineuses). Cela dit, cet article présente une solution qui semble atteindre des centaines de GFLOPS / s sur des masortingces de ma taille en déroulant de manière agressive des boucles et en effectuant la factorisation complète dans des registres, en soulignant que les dimensions de la masortingce sont définies à la compilation. (Cette boucle devrait permettre de réduire la surcharge et d’améliorer la vectorisation dans la version Phi, donc getIndices() deviendra plus important!) Je pense donc que mon kernel devrait ressembler plus à getIndices() :

 double *data; // move data to GPU/Phi once into shared memory template double doTestUnrolled(int *idx) { double submasortingx[K][K]; // copy data into submasortingx #pragma unroll for(int i=0; i<K; i++) { #pragma unroll for(int j=0; j<K; j++) { submatrix[i][j] = data[idx[i]][idx[j]]; } } factorizeUnrolled(submasortingx); return the_answer; } 

La version Phi résout chaque modèle dans une boucle `cilk_for ‘du modèle = 0 à 2 ^ N (ou, plutôt, un sous-ensemble pour tester), mais maintenant pour traiter par lots pour le GPU et amortir la charge de lancement du kernel itérer les numéros de modèle dans l’ordre lexicographique pour chacun des K = 1 à 41 bits mis en place (comme noté doynax).

EDIT 2: Maintenant que les vacances sont terminées, voici quelques résultats sur mon Xeon E5-2602 utilisant la version 15 d’icc. Le code que j’ai utilisé pour évaluer est ici: http://pastebin.com/XvrGQUat . J’exécute l’extraction de bits sur des nombres entiers ayant exactement K bits définis, il y a donc une surcharge pour l’itération lexicographique mesurée dans la colonne “Base” du tableau ci-dessous. Celles-ci sont effectuées 2 ^ 30 fois avec N = 48 (en répétant si nécessaire).

“CTZ” est une boucle qui utilise le __builtin_ctzll insortingnsèque de gcc pour obtenir le bit de plus petit ordre défini:

 for(int i=0; i<K; i++) { idx[i] = __builtin_ctzll(tmp); lb = tmp & -tmp; // get lowest bit tmp ^= lb; // remove lowest bit from tmp } 

Mark est sans twig pour la boucle:

 for(int i=0; i>= 1; } 

Tab1 est mon code basé sur la table d’origine avec la macro de copie suivante:

 #define COPY(d, s, n) \ switch(n) { \ case 8: *(d++) = *(s++); \ case 7: *(d++) = *(s++); \ case 6: *(d++) = *(s++); \ case 5: *(d++) = *(s++); \ case 4: *(d++) = *(s++); \ case 3: *(d++) = *(s++); \ case 2: *(d++) = *(s++); \ case 1: *(d++) = *(s++); \ case 0: break; \ } 

Tab2 est le même code que Tab1, mais la macro de copie ne déplace que 8 octets en une seule copie (en prenant les idées de doynax et Lưu Vĩnh Phúc … mais notez que cela ne garantit pas l’ alignement):

 #define COPY2(d, s, n) { *((uint64_t *)d) = *((uint64_t *)s); d+=n; } 

Voici les résultats. Je suppose que mon affirmation initiale que Tab1 est 3 fois plus rapide que CTZ ne vaut que pour les gros K (où je testais). La boucle de Mark est plus rapide que mon code d’origine, mais se débarrasser de la twig dans la macro COPY2 prend le gâteau pour K> 8.

  K Base CTZ Mark Tab1 Tab2 001 4.97s 6.42s 6.66s 18.23s 12.77s 002 4.95s 8.49s 7.28s 19.50s 12.33s 004 4.95s 9.83s 8.68s 19.74s 11.92s 006 4.95s 16.86s 9.53s 20.48s 11.66s 008 4.95s 19.21s 13.87s 20.77s 11.92s 010 4.95s 21.53s 13.09s 21.02s 11.28s 015 4.95s 32.64s 17.75s 23.30s 10.98s 020 4.99s 42.00s 21.75s 27.15s 10.96s 030 5.00s 100.64s 35.48s 35.84s 11.07s 040 5.01s 131.96s 44.55s 44.51s 11.58s 

Je pense que la clé de la performance ici est de se concentrer sur le problème plus large plutôt que sur la micro-optimisation de l’extraction des positions de bits à partir d’un entier aléatoire.

A en juger par votre exemple de code et votre précédente question SO, vous enregistrez tous les mots avec K bits définis dans l’ordre, et vous en extrayez les indices de bits. Cela simplifie grandement les choses.

Si c’est le cas, au lieu de reconstruire la position du bit, chaque itération essaye d’incrémenter directement les positions dans le tableau de bits. La moitié du temps, cela impliquera une itération et un incrément de boucle unique.

Quelque chose dans ce sens:

 // Walk through all len-bit words with num-bits set in order void enumerate(size_t num, size_t len) { size_t i; unsigned int bitpos[64 + 1]; // Seed with the lowest word plus a sentinel for(i = 0; i < num; ++i) bitpos[i] = i; bitpos[i] = 0; // Here goes the main loop do { // Do something with the resulting data process(bitpos, num); // Increment the least-significant series of consecutive bits for(i = 0; bitpos[i + 1] == bitpos[i] + 1; ++i) bitpos[i] = i; // Stop on reaching the top } while(++bitpos[i] != len); } // Test function void process(const unsigned int *bits, size_t num) { do printf("%d ", bits[--num]); while(num); putchar('\n'); } 

Pas particulièrement optimisé mais vous obtenez l'idée générale.

Voici quelque chose de très simple qui pourrait être plus rapide – pas de moyen de savoir sans tester. Beaucoup dépendra du nombre de bits défini par rapport au nombre non défini. Vous pouvez dérouler cela pour supprimer les twigments, mais avec les processeurs actuels, je ne sais pas si cela accélèrerait.

 unsigned char idx[K+1]; // need one extra for overwrite protection unsigned char *dst=idx; for (unsigned char i = 0; i < 50; i++) { *dst = i; dst += x & 1; x >>= 1; } 

PS votre sortie échantillon dans la question est incorrecte, voir http://ideone.com/2o032E

En tant que modification minimale:

 int64_t x; char idx[K+1]; char *dst=idx; const int BITS = 8; for (int i = 0 ; i < 64+BITS; i += BITS) { int y = (x & ((1<>= BITS; } 

Le choix de BITS détermine la taille de la table. 8, 13 et 16 sont des choix logiques. Chaque entrée est une chaîne, terminée par zéro et contenant des positions de bit avec 1 décalage. Ie onglet [5] est "\x03\x01" . La boucle interne corrige ce décalage.

Légèrement plus efficace: remplacez le strcat et la boucle interne par

 char const* ptr = tab[y]; while (*ptr) { *dst++ = *ptr++ + (i-1); } 

Le déroulement de la boucle peut être un peu pénible si la boucle contient des twigs, car la copie de ces instructions de twig n’aide pas le prédicteur de twig. Je vais volontiers laisser cette décision au compilateur.

Une chose que je considère, c’est que l’ tab[y] est un tableau de pointeurs vers des chaînes. Celles-ci sont très similaires: "\x1" est un suffixe de "\x3\x1" . En fait, chaque chaîne qui ne commence pas par "\x8" est un suffixe d’une chaîne qui le fait. Je me demande combien de chaînes uniques vous avez besoin et dans quelle mesure l’ tab[y] est en fait nécessaire. Par exemple, par la logique ci-dessus, tab[128+x] == tab[x]-1 .

[modifier]

Bien sûr, vous avez besoin de 128 entrées de tabulation commençant par "\x8" car elles ne sont jamais le suffixe d’une autre chaîne. Cependant, la règle de l’ tab[128+x] == tab[x]-1 signifie que vous pouvez enregistrer la moitié des entrées, mais au prix de deux instructions supplémentaires: char const* ptr = tab[x & 0x7F] - ((x>>7) & 1) . (Configurez l’ tab[] pour qu’il pointe après le \x8 )

Utiliser char ne vous aidera pas à augmenter la vitesse, mais nécessite en fait plus de ANDing et de sign / zero pendant le calcul. Seulement dans le cas de très grands tableaux qui doivent tenir dans le cache, des types int plus petits doivent être utilisés

Vous pouvez également améliorer la macro COPY. Au lieu de copier octet par octet, copiez le mot entier si possible

 inline COPY(unsigned char *dst, unsigned char *src, int n) { switch(n) { // remember to align dst and src when declaring case 8: *((int64_t*)dst) = *((int64_t*)src); break; case 7: *((int32_t*)dst) = *((int32_t*)src); *((int16_t*)(dst + 4)) = *((int32_t*)(src + 4)); dst[6] = src[6]; break; case 6: *((int32_t*)dst) = *((int32_t*)src); *((int16_t*)(dst + 4)) = *((int32_t*)(src + 4)); break; case 5: *((int32_t*)dst) = *((int32_t*)src); dst[4] = src[4]; break; case 4: *((int32_t*)dst) = *((int32_t*)src); break; case 3: *((int16_t*)dst) = *((int16_t*)src); dst[2] = src[2]; break; case 2: *((int16_t*)dst) = *((int16_t*)src); break; case 1: dst[0] = src[0]; break; case 0: break; } 

De plus, comme les tabofs [x] et n [x] sont souvent proches les uns des autres, essayez de les rapprocher en mémoire pour vous assurer qu’ils sont toujours en cache en même temps

 typedef struct TAB_N { int16_t n, tabofs; } tab_n[256]; src=tab0+tab_n[b0].tabofs; COPY(dst, src, tab_n[b0].n); src=tab0+tab_n[b1].tabofs; COPY(dst, src, tab_n[b1].n); src=tab0+tab_n[b2].tabofs; COPY(dst, src, tab_n[b2].n); src=tab0+tab_n[b3].tabofs; COPY(dst, src, tab_n[b3].n); src=tab0+tab_n[b4].tabofs; COPY(dst, src, tab_n[b4].n); src=tab0+tab_n[b5].tabofs; COPY(dst, src, tab_n[b5].n); 

Last but not least, gettimeofday n’est pas destiné au comptage des performances. Utilisez plutôt QueryPerformanceCounter , c’est beaucoup plus précis

Votre code utilise la table d’index 1 octet (256 entrées). Vous pouvez l’accélérer par facteur 2 si vous utilisez une table d’index de 2 octets (65536 entrées).

Malheureusement, vous ne pouvez probablement pas étendre cela plus loin – pour une taille de table de 3 octets serait de 16 Mo, pas susceptible de tenir dans le cache local du processeur, et cela ne ferait que ralentir les choses.

La question est ce que vous allez faire avec la collecte des positions?
Si vous devez l’itérer plusieurs fois, alors oui, il pourrait être intéressant de les rassembler une fois, comme vous le faites maintenant, et de les réitérer.
Mais si c’est pour une itération juste une ou plusieurs fois, alors vous pourriez penser à ne pas créer un tableau intermédiaire de positions, et simplement invoquer une fermeture / fonction de bloc de traitement à chaque 1 rencontré en itérant sur des bits.

Voici un exemple naïf d’iterator de bit que j’ai écrit dans Smalltalk:

 LargePositiveInteger>>bitsDo: aBlock | mask offset | 1 to: self digitLength do: [:iByte | offset := (iByte - 1) << 3. mask := (self digitAt: iByte). [mask = 0] whileFalse: [aBlock value: mask lowBit + offset. mask := mask bitAnd: mask - 1]] 

Un LargePositiveInteger est un entier de longueur arbitraire composé de chiffres d'octet. Le lowBit répond au rang du bit le plus bas et est implémenté en tant que table de consultation avec 256 entrées.

En C ++ 2011, vous pouvez facilement passer une fermeture, il devrait donc être facile à traduire.

 uint64_t x; unsigned int mask; void (*process_bit_position)(unsigned int); unsigned char offset = 0; unsigned char lowBitTable[16] = {0,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0}; // 0-based, first entry is unused while( x ) { mask = x & 0xFUL; while (mask) { process_bit_position( lowBitTable[mask]+offset ); mask &= mask - 1; } offset += 4; x >>= 4; } 

L'exemple est démontré avec un tableau de 4 bits, mais vous pouvez facilement l'étendre à 13 bits ou plus s'il tient dans le cache.

Pour la prédiction de twig, la boucle interne pourrait être réécrite comme un for(i=0;i avec une table supplémentaire nbit=numBitTable[mask] puis déroulée avec un switch (le compilateur pourrait le faire?), Mais je laissez-vous mesurer comment il fonctionne d'abord ...

Cela a-t-il été trouvé trop lent?
Petit et grossier, mais tout est dans les registres cache et CPU;

 void mybits(uint64_t x, unsigned char *idx) { unsigned char n = 0; do { if (x & 1) *(idx++) = n; n++; } while (x >>= 1); // If x is signed this will never end *idx = (unsigned char) 255; // List Terminator } 

Il est encore 3 fois plus rapide de dérouler la boucle et de produire un tableau de 64 valeurs true / false (ce qui n’est pas tout à fait ce que l’on veut)

 void mybits_3_2(uint64_t x, idx_type idx[]) { #define SET(i) (idx[i] = (x & (1UL< 

Voici un code serré, écrit pour 1 octet (8 bits), mais il devrait facilement s’étendre à 64 bits.

 int main(void) { int x = 187; int ans[8] = {-1,-1,-1,-1,-1,-1,-1,-1}; int idx = 0; while (x) { switch (x & ~(x-1)) { case 0x01: ans[idx++] = 0; break; case 0x02: ans[idx++] = 1; break; case 0x04: ans[idx++] = 2; break; case 0x08: ans[idx++] = 3; break; case 0x10: ans[idx++] = 4; break; case 0x20: ans[idx++] = 5; break; case 0x40: ans[idx++] = 6; break; case 0x80: ans[idx++] = 7; break; } x &= x-1; } getchar(); return 0; } 

Le tableau de sortie doit être:

 ans = {0,1,3,4,5,7,-1,-1}; 

Si je prends “j’ai besoin d’un moyen rapide pour obtenir la position de tous les bits dans un entier de 64 bits” littéralement …

Je me rends compte qu’il ya quelques semaines, et par curiosité, je me souviens de mes jours d’assemblage avec le CBM64 et l’Amiga en utilisant un décalage arithmétique et en examinant l’indicateur de portage. clair alors c’est zéro

par exemple pour un décalage arithmétique à gauche (examen du bit 64 au bit 0) ….

 pseudo code (ignore instruction mix etc errors and oversimplification...been a while): move #64+1, counter loop. ASL 64bitinteger BCS carryset decctr. dec counter bne loop exit carryset. //store #counter-1 (ie bit position) in datastruct indexed by counter jmp decctr 

… J’espère que vous aurez l’idée.

Je n’ai pas utilisé d’assemblage depuis, mais je me demande si nous pourrions utiliser un assemblage en ligne C ++ similaire à celui ci-dessus pour faire quelque chose de similaire ici. Nous pourrions faire toute la conversion en assemblage (très peu de lignes de code), en construisant une structure de données appropriée. C ++ pourrait simplement examiner la réponse.

Si cela est possible, j’imagine que c’est assez rapide.

En supposant que le nombre de bits de set est faible,

 int count = 0; unsigned int tmp_bitmap = x; while (tmp_bitmap > 0) { int next_psn = __builtin_ffs(tmp_bitmap) - 1; tmp_bitmap &= (tmp_bitmap-1); id[count++] = next_psn; }