我正在尝试删除NASM中字符串开头的所有'0'
。
mov ecx, "0001" ; ecx now holds 0x31303030
-> code that clears the first 3 zeros so that ecx will hoad 0x31 only
如何移除它们?
看看cl
-如果它不等于'0'
,就完成了,否则将ecx
向右移动一个字节并重复。
remove_zeroes:
cmp cl,'0'
jne done
shr ecx,8
jmp remove_zeroes
done:
如果你能腾出另一个寄存器,这可能会有所改进:
remove_zeroes:
mov eax,ecx
shr ecx,8
cmp al,'0'
je remove_zeroes
mov ecx,eax
假设完成后您希望高字节为0
(而不是'0'
(,即使第二个循环的效率不如预期,@Michael的循环也能工作;做…而";风格(尾部跳跃(?对于标准的高效循环结构,即剥离第一次迭代的比较/分支部分,以便我们可以旋转循环。
cmp cl, '0'
jne .done ; low byte already not ASCII '0'
.remove_zeros:
shr ecx,8
cmp cl,'0'
je .remove_zeros
.done:
还有其他有趣的事情可以做:无分支非循环
例如,使用ctz(ecx ^ '0000')
来获得第一个位差的位置(从寄存器底部开始计数(,然后将其向下舍入为8位的倍数作为移位计数。处理所有4个字节都是'0'
的情况需要一些小心,因为它需要32的移位计数,但BSF无法产生该计数,而32位移位会屏蔽计数,从而使其换行。
然而,将ctz
实现为TZCNT将为全零输入产生32,并且移位的64位操作数大小将使其工作,从而使用&63
而不是&31
。(shr
文档(
mov eax, ecx
xor ecx, '0000' ; or '000' to always keep the highest byte
; jz special_case_all_zero ; optional
tzcnt ecx, ecx ; decodes as BSF on older CPUs, which is ok for non-zero ECX
and ecx, -8 ; clear low 3 bits = round down to multiple of 8
shr rax, cl ; 64-bit shift lets count=32 work
; result in EAX
;mov ecx, eax ; if you need the result back in ECX
例如,ECX="0040"(0x34043030(
'0040' ^ '0000' = '0040' (0x00040000)
BSF / TZCNT = 18
18 & -8 = 16
0x30343030 >> 16 = 0x00003034 = '40' (zero-extended in a dword)
对于ECX='0000'特殊情况,我们得到TZCNT(0(=32。但在一个将TZCNT解码为BSF(忽略REP前缀(的旧CPU上,它不修改目的地,因此我们得到0。(dst未修改的行为由AMD记录,但至少由英特尔和AMD在其当前CPU上实现。(
如果您想将其缩减为'0'
而不是空字符串,那么所有零都是一种特殊情况您可以通过执行xor ecx, '000'
(3个ASCII 0,以二进制0作为高位字节(在没有额外分支的情况下执行此操作。唯一可以移位32的方法是当高字节已经是0
(而不是'0'
(时。即它将保持任何非零高字节。
如果你不需要TZCNT为全零寄存器产生32,那么它可以在没有BMI1的CPU上解码为BSF;另请参阅。因此,尽管使用BMI1指令编写,但此代码在任何x86-64 CPU上都能正常工作。可以使用shr eax, cl
而不是rax
,保存REX前缀。
对于您的ASCII数据,选择任何其他输入寄存器都会更方便,因此当我们在ECX中生成移位计数时,它可以保留在该寄存器中。除非您有BMI2,否则您可以使用shrx ecx, ecx, eax
来执行ecx >>= eax
。它也更高效;即使在Intel CPU上也只有一个uop,其中shr reg, cl
是3,因为遗留的x86行李(如果计数为0,FLAGS必须保持不变(。https://uops.info//https://agner.org/optimize/
您也可以使用xmm1中的SIMDpcmpeqb xmm0, xmm1
/pmovmskb eax, xmm0
/not eax
和'0000'
来执行此操作。这为您提供了一个字节比较掩码(类似于XOR位比较掩码(,您可以对其进行位扫描并按8缩放以转换为移位计数。但这并不比XOR更好;只有当您打算使用SIMD混洗来同时处理16个字节时,才显得有用。
(没有可变计数SIMD字节移位,但也许您可以使用位扫描结果从db 0, 1, ..., 14, 15
/times 16 db -1
的数组中加载一个滑动窗口,以获得pshufb
的混洗控制向量。(
或者对BMI2pext
使用pcmpeqb
/movd eax, xmm0
结果,但这将删除所有零,而不仅仅是低零。要将所有位设置在最低1以上,可能blsi
隔离低位,然后neg
?这可能比使用XOR/TZCNT/and来获得移位计数有更多的指令和更高的延迟,而且即使在延迟为1 uop/3c的英特尔CPU上,移位的延迟也比PEXT低。(相对于在AMD上非常缓慢和微编码(。
具有来自AVX-512BWvpcmpb k, xmm, xmm/m128, _MM_CMPINT_EQ
的掩模的用于vpcompressb xmm1{k1}{z}, xmm2
的AVX-512VBMI2(冰湖(将具有去除所有零的相同问题。