Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Assembly NASM:计算32位数字中有多少位被设置为1_Assembly_X86_Bit Manipulation_Nasm_Hammingweight - Fatal编程技术网

Assembly NASM:计算32位数字中有多少位被设置为1

Assembly NASM:计算32位数字中有多少位被设置为1,assembly,x86,bit-manipulation,nasm,hammingweight,Assembly,X86,Bit Manipulation,Nasm,Hammingweight,我有一个32位的数字,我想知道有多少位是1 我想到了这个伪代码: mov eax, [number] while(eax != 0) { div eax, 2 if(edx == 1) { ecx++; } shr eax, 1 } 有没有更有效的方法 我在x86处理器上使用NASM (我刚刚开始使用汇编程序,所以请不要告诉我使用外部库中的代码,因为我甚至不知道如何包含它们;) (我刚刚发现其中也包含我的解决方案。发布了其他解决方案,但不幸的是,我似乎不知道如何在a

我有一个32位的数字,我想知道有多少位是1

我想到了这个伪代码:

mov eax, [number]
while(eax != 0)
{
  div eax, 2
  if(edx == 1)
  {
   ecx++;
  } 
  shr eax, 1
}
有没有更有效的方法

我在x86处理器上使用NASM

(我刚刚开始使用汇编程序,所以请不要告诉我使用外部库中的代码,因为我甚至不知道如何包含它们;)


(我刚刚发现其中也包含我的解决方案。发布了其他解决方案,但不幸的是,我似乎不知道如何在assembler中编写它们)

最有效的方法(无论如何,就执行时间而言)是创建一个查找表。显然,你不会有一个40亿条目的表,但是你可以把32位分解成8位的块,只需要一个256条目的表,或者进一步分解成4位的块,只需要16条。祝你好运

我的x86汇编程序有点生疏,但我想到的是:

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
包含您的位计数


CF
设置为最后一位移出,从中读取它。

在支持SSE4的处理器中,您有执行此操作的POPCNT指令

最简单的算法实际上比你想象的要快(DIV指令真的很慢)

关于您对之前SO答案的评论,我将从中选取一个示例答案,并引导您了解我将如何转换它

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;
}
(我假设你知道如何定义函数和有趣的东西)。 需要的是一个非常简单的循环、一个计数器变量(传统上,ecx既是索引又是计数器)和位测试指令

    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

在汇编中实现类似于汉明权重算法的算法并不复杂,但足够复杂,因此您不希望将其作为初始作业问题来完成。

此程序提供32位数字中的1数。试用:)

使用bsf(位向前扫描)可能比普通移位更有效率

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:
最好的方法是:

tabx:array [0..255] of byte = //number of bit for each byte (COPY THIS TABLE)
    (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);

In MASM:
asm

作为记录,如果您希望获得良好的性能,通常希望通过8位表查找或乘法位破解(GCC当前针对
\uuuuu builtin\u popcnt
而不使用
-mpopcnt
的标量回退)来避免循环/分支。如果您的数字通常很小(右移1),或者您的数字通常只设置了几个位(使用
x&(x-1)
清除最低设置位时循环),则循环几乎不正常。但对于设置了一半或一半以上位的数字,它们的性能相当差


大多数现代x86 CPU都支持。SSE4.2暗示了这一点,但它也有自己的CPUID特性位,因此CPU可以在没有SSE4.2的情况下使用它。Intel Core 2及更高版本没有此功能

xor     eax,eax     ; avoid false dependency on Sandybridge-family before IceLake
popcnt  eax,  edi
如果您不介意覆盖同一寄存器,例如,
popcnt edi,edi
可以避免输出错误依赖的危险:您已经在同一寄存器上有了真正的依赖。()


如果没有HW
popcnt
另一个选项是SSSE3
pshufb
,它实际上非常适合计算大型数组,特别是如果您有AVX2。看

  • 以及中的其他链接

使用基准x86指令的回退 可以进行数组查找,使用
movzx-ecx、al
/
movzx-edx、ah
/
shr-eax、16
等提取每个字节。然后
movzx-ecx、[table+rcx]
/
添加cl、[table+rdx]
。请注意,总结果最多为64,因此不会使8位寄存器溢出。这需要一个256字节的表才能在缓存中保持热状态以获得良好的性能。如果你做了很多popcnt但不能使用SIMD,这可能是一个不错的选择;针对您的用例,针对bithack对其进行基准测试

如果在编译时未启用HW popcnt,则来自/的bithack是GCC当前使用的。(即在libgcc助手函数中)。请参阅该答案,了解bithack如何/为什么将位相加为2位累加器,然后再次水平相加为4位累加器,等等。(有趣的事实:GCC和clang实际上认识到C逻辑是一种popcnt习惯用法,并使用
-mpopcnt
将其编译成
popcnt
指令。下面的asm没有-mpopcnt;我看不到任何手动改进它的方法。 它尽可能地使用EAX作为目标,并允许
和EAX,imm32
不带modrm字节的短格式。)

这是一个非分支代码,不需要任何数据查找,因此它不会缓存未命中的数据(I-cache除外),如果您关心popcount性能(特别是延迟),但不经常这样做以使查找表在缓存中保持热状态,那么它可能会很好。(对于64位整数,64位版本的查找可能比8x字节查找更好。)

对于64位整数,它是相同的序列,以64位乘法结束。(但您需要
mov reg,imm64
来具体化64位掩码和乘法器常量;它们不会直接作用于and或IMUL)

像RORX这样的指令可能有助于更有效地复制和移位,而不是mov/shr,但是任何带有RORX的CPU也会有POPCNT,所以您应该使用它!LEA to copy和left shift没有帮助:加法从低到高进行传播,因此为了避免在第一步中丢失顶部的位,您需要右移位ode>>>2步骤也无法添加到每对2位累加器中较高的累加器中:该点的最大和为
4
,需要3位来表示,因此,如果执行
lea eax,[rdi+rdi],最高累加器(位于寄存器顶部)可能会丢失计数
/2x和/add,因为它只有2位,而不是4位未对齐。并且最终需要右移位,以便在imul之前的某个点将计数器放回字节底部,因此即使在前面的步骤中可以使用左移位/add,也会延长关键路径延迟

循环
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:
tabx:array [0..255] of byte = //number of bit for each byte (COPY THIS TABLE)
    (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);

In MASM:
asm
mov   eax,number //32 bit 
movzx ecx,tabx[al] //for clear ecx except cl
addb  cl,tabx[ah]  //add ah to cl  
shr   eax,16  //put left part in ah-al
addb  cl,tabx[al]
addb  cl,tabx[ah]
mov   result,ecx

xor     eax,eax     ; avoid false dependency on Sandybridge-family before IceLake
popcnt  eax,  edi
; x86-64 System V calling convention
; but also of course works for 32-bit mode with the arg in a register
numberOfSetBits:     ; 32-bit unsigned int x    in EDI
    mov    eax, edi
    shr    eax, 1
    and    eax, 0x55555555          ; (x>>1) & 0x55555555
    sub    edi, eax                 ; x -= ((x>>1) & 0x55555555)   2-bit sums

    mov    eax, edi
    shr    edi, 0x2
    and    eax, 0x33333333
    and    edi, 0x33333333
    add    edi, eax                 ; pairs of 2-bit accumulators -> 4

    mov    eax, edi
    shr    eax, 0x4
    add    eax, edi                 ; we can add before masking this time without overflow risk
    and    eax, 0x0f0f0f0f

    imul   eax, eax, 0x01010101       ; sum the 4 bytes into the high byte (because their values are small enough)
    shr    eax, 24
    ret    
;;;   Good for small inputs (all set bits near the bottom)
;; input: EDI  (zeroed when we're done)
;; output: EAX = popcnt(EDI)
popcount_shr_loop:
    xor   eax, eax
  ; optional: make the first adc non-redundant.  Otherwise just fall into the loop (with CF=0 from xor)
    shr   edi, 1         ; shift low bit into CF
                 ;; jz .done   ; not worth running an extra instruction for every case to skip the loop body only for the input == 0 or 1 case
 .loop:
    adc   eax, 0         ; add CF (0 or 1) to result
    shr   edi, 1
    jnz   .loop          ; leave the loop after shifting out the last bit
 ;.done:
    adc   eax, 0         ; and add that last bit
    ret
popcount_shr_loop_unroll2:
    xor   eax, eax
    shr   edi, 1         ; shift low bit into CF
          ;; jz .done     ; still optional, but saves more work in the input <= 1 case.  Still not worth it unless you expect that to be very common.
 .loop:
%rep 2            ;; Unroll
    adc   eax, 0         ; add CF (0 or 1) to result
    shr   edi, 1
%endrep           ;; still ending with ZF and CF set from a shift
    jnz   .loop          ; leave the loop on EDI == 0
 ;.done:
    adc   eax, 0         ; there may still be a bit we haven't added yet
    ret
  ;; could be good if very few bits are set, even if they're scattered around
;; Input: EDI  (zeroed when done)
;; output: EAX = popcount(EDI)
;; clobbers: EDX
popcount_loop_lsr:
    xor  eax,eax
    test edi,edi
    jz   .done            ; if(!x) return 0;
 .loop:                   ; do{
    inc  eax                 ; ++count
    lea  edx, [rdi-1]
    and  edi, edx            ; x &= x-1  clear lowest set bit
    jnz  .loop            ; }while(x)

 .done:
    ret