从 x64 NASM 中的字符串中清除所有起始 ASCII 0



我正在尝试删除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前缀。

注意,它被称为TZCNT,因为寄存器的低位是"0";拖尾";位。但是x86是小端序,所以在处理字符串时,打印顺序中的第一个字节(最低内存地址(是整数值的最高8位。不过,BSF(位扫描转发(的命名确实遵循little-endian。

对于您的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(冰湖(将具有去除所有零的相同问题。

相关内容

  • 没有找到相关文章

最新更新