我找到了以下代码,用于在最小数量的指令中计算Arm组件中4字节整数的位:
;R0 - value
;R1 = number of ones
;Uses R0-R1
;81 cycles worst case, 4 cycles best case, exit when r1=0
mov r1,r0,lsr #31
loop movs r0,r0,lsl #2
adc r1,r1,r0,lsr #31
bne loop
mov pc,r14
您能解释一下这里的算法是什么吗?虽然我知道所有指令都做了什么,但我无法理解。
答案 0 :(得分:2)
mov r1,r0,lsr #31 @ start with r1 = the high bit of r0 (right shift by 31 bits)
loop movs r0,r0,lsl #2 @ left shift r0 by 2, and set flags on the result
adc r1,r1,r0,lsr #31
bne loop @ loop if r0 is non-zero (testing flags set by movs)
add-with-carry是一个巧妙的技巧:r0 >> 31
是高位,进位标志是由movs r0,r0,lsl #2
移出的位(我假设ARM以这种方式工作x86,否则算法没有意义。)
因此每次迭代会向popcnt总计增加2位:高位,最后一位向外移位。
这是不是 popcnt的最快方式。
上一个问题:Fastest way to count number of 1s in a register, ARM assembly解决了这个问题,一个答案使用vcnt.8 d0, d0
,然后是四个8位计数的水平和。其中没有一个答案提到这个两位一次循环。
即使没有LUT(例如8位LUT来查找每个字节的计数)或专用的popcnt指令,也存在bithacks,like this one from Sean Anderson's collection:
v = v - ((v >> 1) & 0x55555555); // reuse input as temporary
v = (v & 0x33333333) + ((v >> 2) & 0x33333333); // temp
c = ((v + (v >> 4) & 0xF0F0F0F) * 0x1010101) >> 24; // count
恒定时间没有分支,但确实需要几个32位常量(每个需要两条指令才能进入ARM上的regs)