2010-05-28 11 views
4

J'ai un 32 numéro de Bit et que vous souhaitez compter savoir combien de bits sont 1.MSNA: Comptez le nombre de bits dans un nombre de 32 bits sont mis à 1

Je pense à ce pseudo-code:

mov eax, [number] 
while(eax != 0) 
{ 
    div eax, 2 
    if(edx == 1) 
    { 
    ecx++; 
    } 
    shr eax, 1 
} 

Existe-t-il un moyen plus efficace? Je suis en utilisant NASM sur un processeur x 86.

(je suis juste en commençant par l'assembleur, donc s'il vous plaît ne me dites pas d'utiliser le code des bibliothèques externat, parce que je ne sais même pas comment les inclure;))

(Je viens de découvrir How to count the number of set bits in a 32-bit integer? qui a également contient ma solution.Il existe d'autres solutions publiées, mais malheureusement je n'arrive pas à comprendre, comment je les écrirais en assembleur)

+0

Il est évident que vous ne devriez pas utiliser réellement 'div', [qui est l'un des plus lents instructions entières] (https://stackoverflow.com/questions/40354978/why-is-this-c-code-faster -than-ma-main-écrite-assembly-for-testing-the-collat ​​/ 40355466 # 40355466). Vérifiez simplement le peu de EAX avec 'test al, 1'. Ou 'shr eax, 1' /' adc ecx, 0' serait un moyen efficace d'implémenter ce pseudo-code. –

Répondre

7

Le moyen le plus efficace (en termes de temps d'exécution, de toute façon) est d'avoir un table de recherche. Évidemment, vous n'allez pas avoir une table d'entrée de 4 milliards, mais vous pouvez décomposer les 32 bits en morceaux de 8 bits et avoir seulement besoin d'une table de 256 entrées, ou plus bas en morceaux de 4 bits et seulement 16 entrées . Bonne chance!

+0

Si le coût initial est un problème, vous pouvez créer la table de recherche au fur et à mesure. Vous savez qu'une seule entrée aura une valeur de 0 1, et c'est 0x00. Par conséquent, si une entrée dans la table de recherche est 0, vous savez que vous devez compter celle-ci, mais une fois que vous l'avez calculée une fois, vous pouvez la stocker là. De cette façon, vous n'avez pas besoin de compter tous les 256 lorsque vous démarrez. – corsiKa

+0

@glowcoder, c'est une bonne suggestion. Cette question ressemble à un problème de devoirs, cependant, je pense que c'est un peu exagéré. Je dirais que c'est beaucoup moins compliqué de pré-générer la table. –

+1

Vous pouvez effectuer un comptage de population de 32 bits dans les instructions 15 à 20 (voir par exemple Hacker's Delight de Warren). Briser le mot en morceaux de 8 bits, faire 4 recherches de table et ensuite additionner les 4 résultats ne sera probablement pas aussi efficace que cela, et il ne se prête pas à l'optimisation, par ex. SIMD, GPGPU, etc. –

4

Mon assembleur x86 est un peu rouillé, mais cela vient à l'esprit:

clc   ; clear carry 
xor ecx, ecx ; clear ecx 

shl eax, 1  ; shift off one bit into carry 
adc ecx, 0  ; add carry flag to ecx 
; ... repeat the last two opcodes 31 more times 

ecx contient votre nombre de bits.

x86 shift instructions définir CF sur le dernier bit décalé, où adc ecx, 0 le lit.

+0

Vous n'avez pas besoin de 'clc' car' shl eax' place inconditionnellement 'CF' sur le bit décalé. 'adc' est probablement la meilleure façon d'implémenter la méthode naïve, mais vous pouvez quitter la boucle quand' eax' devient zéro, plutôt que de toujours faire 32 itérations. Cependant, tout type de boucle bit à la fois est significativement plus lent que les meilleures options [bithack] (https://graphics.stanford.edu/~seander/bithacks.html) ou LUT ('pshufb'). –

5

Dans les processeurs prenant en charge SSE4, vous disposez de l'instruction POPCNT pour vous.

L'algorithme le plus naïf est en réalité plus rapide que ce que vous avez imaginé (les instructions DIV sont vraiment lentes).

mov eax, [number] 
xor ecx,ecx 
loop_start: 
    test eax,1 
    jnz next 
    inc ecx 
next: 
    shr eax, 1 
    mov eax,ecx 

En ce qui concerne vos commentaires sur les réponses précédentes, je vais prendre un exemple de réponse à partir de là et vous guidera à travers la façon dont je vais le convertir.

long count_bits(long n) {  
    unsigned int c; // c accumulates the total bits set in v 
    for (c = 0; n; c++) 
    n &= n - 1; // clear the least significant bit set 
    return c; 
} 

(Je vais supposer que vous savez comment définir une fonction et des trucs amusants comme ça). Ce qui est nécessaire est une boucle très simple, une variable de compteur (traditionnellement, ecx est à la fois l'index et un compteur), et des instructions de test de bits.

mov edx,n 
    xor ecx,ecx 
loop_start: 
    test edx,edx 
    jz end 
    mov ebx,edx 
    dec ebx 
    and edx,ebx 
    inc ecx 
    jmp loop_start 
end: 
    mov eax,ecx 
    ret 

La mise en œuvre quelque chose comme l'algorithme de poids Hamming dans l'assemblage est pas compliqué, mais il est juste assez compliqué que vous préférez ne pas le faire comme un problème de travail initial.

0

Ce programme vous donne le nombre de 1 dans un nombre de 32 bits. Essayez :)

extern printf      
SECTION .data     
msg: db "The number of 1 bits are: %d",10,0 
inta1: dd 1234567 
num: dd 2147483647 
SECTION .text      

global main     
main:  
    mov eax, [num] 
    mov ecx,32 
    mov edx,0 
.loop: dec ecx 
    cmp ecx,0 
    jl .exit 
    shr eax,1 
    jnc .loop 
    inc edx 
jmp .loop 
.exit: 
    push edx 
    push dword msg   
    call printf    
    add  esp, 8 
+0

Voir aussi [@ réponse ChrisDodd très similaire] (http://stackoverflow.com/a/37171656/224132) à une question de cet utilisateur sur la façon de compter les bits. (Ce n'est pas du plagiat, cependant, puisque la logique est différente et moins efficace, et le programme 'main' qui l'entoure est un travail original.) Notez aussi qu'une instruction' ret' à la fin de cette commande ne ferait pas planter . –

1
 mov eax,[c] 
     xor ebx,ebx 
SSS: shr eax,1 ; after shift, if eax=0 ZF flag=1 
     jz XXX  ; end (no more bit on eax) 
     adc bl 
     jmp SSS 
XXX: adc bl 
     movb [Nbit],bl 
+0

Vous pouvez modifier la boucle pour avoir seulement un 'jnz' en bas, au lieu d'un' jmp' et un 'jz'. En entrée, sautez sur le 'shr' au milieu de la boucle. SSS: 'adc' /' shr'/'jnz SSS' /' adc'. Comme il est possible de faire une itération supplémentaire, vous pouvez également éplucher certaines itérations déroulées au début pour que vous puissiez tomber dans la boucle. par exemple. 'mov ebx, eax' /' et ebx, 1'/'shr eax, 2'/puis tombent dans la boucle pour le premier' adc'. Bien sûr, si vous vous souciez des performances, vous n'utiliserez pas cette boucle naïve (à moins que vos valeurs soient presque toujours 0 à 3 ou quelque chose, alors que cela peut être plus rapide que les bithacks) –

0

Utilisation de BSF (Bit balayage avant) est probablement un peu plus efficace que le déplacement ordinaire.

xor   edx,edx 
mov   eax,num 
bsf   ecx,eax 
je   end_bit_count 
; align? 
loop_bit_count: 
inc   ecx 
inc   edx 
shr   eax,cl 
bsf   ecx,eax 
jne   loop_bit_count 
end_bit_count: 
+0

Probablement oui pour les entrées avec peu de bits mais où ces bits sont clairsemés au lieu de groupés à la fin qui est déplacé en premier. Mais notez que le nombre de variables 'shl' coûte 3 uops sur la famille Sandybridge, et que' bsf' a une fausse dépendance à la sortie, donc voici une chaîne de dépendances bouclée sur 'ecx'. https://stackoverflow.com/questions/21390165/why-does-breaking-the-output-dependency-of-lzcnt-matter. (Bien que cette chaîne de déphasage à 2 cycles ne soit peut-être pas un goulot d'étranglement.) –

+0

Quoi qu'il en soit, utiliser le bithack 'n & (n-1)' pour effacer le bit le plus bas va être meilleur que BSF/SHR. Faites-le avec 'ecx'/lea edx, [rax-1]'/'et eax, edx' /' jnz loop_bit_count' (avec une coche pour passer la boucle si initial eax = 0, ou pour mettre l'ecx initial sur -1 si l'entrée est zéro). Ou utilisez BMI1 'blsr' pour faire le' n & (n-1) 'dans une instruction qui définit ZF. –

+0

** Mais une implémentation sans bouclage est presque certainement la meilleure solution si vous vous souciez de l'optimisation **, car la mauvaise prédiction de branchement tue les performances avec une dérivation dépendant des données sauf si les modèles sont très prévisibles. (L'idée de votre réponse est de boucler 'popcnt (n)' fois, plutôt que 32 fois.) [Le bithack impliquant une multiplication pour déplacer les bits où ils appartiennent] (https://stackoverflow.com/questions/ 109023/comment compter le nombre de bits dans un entier de 32 bits) est très bon, et peut être implémenté efficacement dans x86 asm (par un compilateur si vous voulez). –

Questions connexes