repne scasbisn't faster than a plain byte-at-a-time loop, unfortunately.
最好用向量指令扫描起始字节:
使用pcmpeqb 一次检查整个向量是否有匹配的起始字节。使用匹配的位位置作为偏移量来加载完整的匹配候选。 (未对齐的负载远比尝试进行数据相关的移位或随机播放更容易,因为palignr 仅可用于立即计数。索引pshufb 随机播放掩码表是可能的,但无济于事,因为无论如何您都需要加载更多内容。
# load your search pattern into xmm4
#broadcast the first byte to every byte of xmm5
# then
.loop:
...
vpcmpeqb xmm0, xmm5, [rsi]
vpmovmskb ecx, xmm0
test ecx,ecx
jnz .found_a_0x39_byte
.resume_search:
add rsi, 16
cmp rsi, rdi # end pointer
jb .loop
...
.found_a_0x39_byte
bsf edx, ecx
vpcmpeqb xmm0, xmm4, [rsi+rdx] ; check against the full pattern (unaligned load, use movdqu if implementing without avx)
vpmovmskb eax, xmm0
; eax has a one bit for every matching byte
; "39 35 ?? ?? ?? ?? 75 10 6A 01 E8"
;0b 1 1 0 0 0 0 1 1 1 1 1 reversed because little endian
not eax ; 0 bits are matching bytes
test eax, 0b11111000011 ; check that all bits we care about are zero
jnz .try_again_with_next_set_bit_in_ecx ; TODO implement this loop
# .found_match:
add rdx, rsi ; pointer to the start of the match
您需要遍历 ecx 中设置的位位置,以检查所有候选起点。或者可以通过检查模式的第二个字节,将该位掩码左移一位,然后将其与第一个位掩码进行“与”来细化。然后你会得到一个只有 0x39 后跟 0x35 的位置的掩码。
循环设置位:BMI1 的BLSR 将清除源中的最低设置位,如果结果为零,则设置ZF。这可能会有所帮助。 (它还设置CF 如果源是零开始,但这在这里没有用)。如果你不能使用 BMI1,there are other ways to clear the lowest bit。
请注意,bsf 如果输入为零,则设置 ZF,即使在这种情况下未定义输出寄存器。 (在这种情况下,使用 BMI1 的 tzcnt 获得 32 或 64 的保证结果。在 C 语言中更有用(其中函数不能返回值和布尔值),但并不总是对 asm 的改进.)
你可能很容易在内存带宽上遇到瓶颈,所以可以做类似的事情
vpcmpeqw xmm0, xmm5, [rsi]
vpcmpeqw xmm1, xmm5, [rsi+1]
仅在找到候选的两字节序列时才退出主搜索循环。不过,这将导致 Sandybridge 的 L1 中的缓存库冲突。它只能从 128B 块的相同 1/8(2 个高速缓存行)中为每个时钟提供一个负载。英特尔 Haswell 及更高版本没有缓存库冲突。理论上,SnB 可能通过仅使用对齐负载并使用palignr 获得未对齐负载进行第二次检查而获胜。这很可能。在只有一个加载端口的 pre-SnB 上表现出色,并且您还希望将数据用于对齐检查。
为了利用库函数来完成繁重的工作,GNU libc 提供了memmem。它类似于strstr,但采用显式大小而不是对以空字符结尾的字符串进行操作。您在 Windows 上,但也许有一个类似的函数具有矢量优化实现。在75 10 6A 01 E8 序列上使用它来寻找潜在的最终候选者。
在块之间的边界,也许只是做一些手动的一次字节检查?或者使用palignr 以两种可能的方式将一个块的最后 16B 与下一个块的前 16B 结合起来?
如果从块的末尾有一个小于 11B 的 0x39,也许只做palignr?