Exi*_*ile 2 x86 assembly sse micro-optimization sse4
这是我的代码汇编程序
你能用c ++嵌入它并检查SSE4吗?速度快
我非常希望看到如何进入SSE4的发展.或者根本不担心他?我们检查一下(我没有SSSE3以上的支持)
{ sse2 strcmp WideChar 32 bit }
function CmpSee2(const P1, P2: Pointer; len: Integer): Boolean;
asm
push ebx // Create ebx
cmp EAX, EDX // Str = Str2
je @@true // to exit true
test eax, eax // not Str
je @@false // to exit false
test edx, edx // not Str2
je @@false // to exit false
sub edx, eax // Str2 := Str2 - Str;
mov ebx, [eax] // get Str 4 byte
xor ebx, [eax + edx] // Cmp Str2 4 byte
jnz @@false // Str <> Str2 to exit false
sub ecx, 2 // dec 4
{ AnsiChar : sub ecx, 4 }
jbe @@true // ecx <= 0 to exit true
lea eax, [eax + 4] // Next 4 byte
@@To1:
movdqa xmm0, DQWORD PTR [eax] // Load Str 16 byte
pcmpeqw xmm0, DQWORD PTR [eax+edx] // Load Str2 16 byte and cmp
pmovmskb ebx, xmm0 // Mask cmp
cmp ebx, 65535 // Cmp mask
jne @@Final // ebx <> 65535 to goto final
add eax, 16 // Next 16 byte
sub ecx, 8 // Skip 8 byte (16 wide)
{ AnsiChar : sub ecx, 16 }
ja @@To1 // ecx > 0
@@true: // Result true
mov eax, 1 // Set true
pop ebx // Remove ebx
ret // Return
@@false: // Result false
mov eax, 0 // Set false
pop ebx // Remove ebx
ret // Return
@@Final:
cmp ecx, 7 // (ebx <> 65535) and (ecx > 7)
{ AnsiChar : cmp ecx, 15 }
jae @@false // to exit false
movzx ecx, word ptr @@mask[ecx * 2 - 2] // ecx = mask[ecx]
and ebx, ecx // ebx = ebx & ecx
cmp ebx, ecx // ebx = ecx
sete al // Equal / Set if Zero
pop ebx // Remove ebx
ret // Return
@@mask: // array Mersenne numbers
dw $000F, $003F, $00FF, $03FF, $0FFF, $3FFF
{ AnsiChar
dw 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383
}
end;
Run Code Online (Sandbox Code Playgroud)
Semple 32bit https://vk.com/doc297044195_451679410
Pet*_*des 11
你调用了你的函数strcmp,但你实际实现的是需要对齐的memcmp(const void *a, const void *b, size_t words).无论movdqa和pcmpeqw xmm0, [mem]如果指针不16B对齐将发生故障.(实际上,如果a+4不是16B对齐,因为你执行前4个标量并增加4个字节.)
使用正确的启动代码movdqu,您可以处理任意对齐(达到要用作内存操作数的指针的对齐边界pcmpeqw).为方便起见,您可能要求两个指针都以宽字符对齐开头,但您不需要(特别是因为您只是返回true/false,而不是negative / 0 /
positive排序顺序.)
你问的是SSE2对战的表现,pcmpeqw对pcmpistrm吧?(显式长度SSE4.2指令的pcmpestrm吞吐量比隐式长度版本更差,因此当您不接近字符串末尾时,请在主循环中使用隐式长度版本.请参阅Agner Fog的指令表和微型指南).
对于memcmp(或仔细实现的strcmp),使用SSE4.2可以做到的最好速度比在大多数CPU上使用SSE2(或SSSE3)所做的最好.也许对于非常短的字符串很有用,但对于memcmp的主循环却没有用.
在Nehalem上:pcmpistri是4 uops,2c吞吐量(带有内存操作数),因此没有其他循环开销,它可以跟上内存.(Nehalem只有1个加载端口). pcmpestri有6c吞吐量:慢3倍.
在Sandybridge通过Skylake,pcmpistri xmm0, [eax]有3c吞吐量,所以它是一个因素太慢,无法跟上每个时钟1个向量(2个加载端口). pcmpestri大多数都具有4c吞吐量,所以它没有那么糟糕.(可能对最后的部分向量有用,但在主循环中没有用).
在Silvermont/KNL上,pcmpistrm速度最快,每14个周期吞吐量运行一次,因此对于简单的东西来说,这是完全垃圾.
在AMD Jaguar上,pcmpistri是2c吞吐量,因此它实际上可以使用(只有一个加载端口). pcmpestri是5c吞吐量,所以很糟糕.
在AMD Ryzen上, pcmpistri也是2c吞吐量,所以它就是废话.(2个加载端口和每个时钟5个uop前端吞吐量(如果有任何(或全部?)6个uop来自多uop指令)意味着你可以更快.
在AMD Bulldozer系列上,pcmpistri直到Steamroller才有3c吞吐量,其中它是5c. pcmpestri拥有10c吞吐量.它们被微编码为7或27 m-ops,因此AMD没有在它们上花费大量的硅.
在大多数的CPU,但如果你的东西,你不能只是做考虑他们的充分利用是唯一值得pcmpeq/pmovmskb.但是如果你可以使用AVX2或特别是AVX512BW,即使做更复杂的事情也可能会更快,更广泛的矢量更多指令.(没有更宽版本的SSE4.2字符串指令.)也许SSE4.2字符串指令对于通常处理短字符串的函数仍然有用,因为宽向量循环通常需要更多的启动/清理开销.此外,在一个不花费很多时间在SIMD循环中的程序中,在一个小功能中使用AVX或AVX512仍然会在下一毫秒左右降低最大turbo时钟速度,并且很容易成为净损失.
一个好的内部循环应该是负载吞吐量的瓶颈,或者尽可能接近. movqdu/ pcmpeqw [one-register]/ pmovmskb/ macro-fused-cmp + jcc只有4个融合域uops,所以这几乎可以在Sandybridge家族CPU上实现
有关实现和一些基准测试,请参阅https://www.strchr.com/strcmp_and_strlen_using_sse_4.2,但这是针对C样式的隐式长度字符串,您必须检查0字节.看起来你正在使用显式长度的字符串,所以在检查长度相等之后,它就是memcmp.(或者我想如果您需要找到排序顺序而不是等于/不等于,则必须将memcmp输出到较短字符串的末尾.)
对于具有8位字符串的strcmp,在大多数CPU上,不使用SSE4.2字符串指令会更快.有关某些基准测试(隐式长度字符串版本),请参阅strchr.com文章的评论.例如,glibc不使用SSE4.2字符串指令strcmp,因为它们在大多数CPU上都不会更快.strstr尽管如此,他们可能会获胜.
glibc有几个SSE2/SSSE3 asm strcmp和memcmp实现.(它是LGPLed,因此你不能只将它复制到非GPL项目中,而是看看它们做了什么.)一些字符串函数(如strlen)每64字节只有一个分支,然后再回来整理缓存行中的哪个字节有命中.但他们的memcmp实现只是用movdqu /展开pcmpeqb.你可以使用,pcmpeqw因为你想知道第一个16位元素的位置是不同的,而不是第一个字节.
您的SSE2实施可能更快.您应该使用带有movdqa的索引寻址模式,因为它不会与pcmpeqw微融合(在Intel Sandybridge/Ivybridge上;在Nehalem或Haswell +上很好),但是pcmpeqw xmm0, [eax]会保持微融合而不会发生分层.
您应该展开几次以减少循环开销.您应该将指针增量与循环计数器结合使用,cmp/jb而不是sub/ja:在更多CPU上进行宏融合,并避免编写寄存器(减少寄存器重命名所需的物理寄存器数量).
您在英特尔Sandybridge/Ivybridge上的内循环将会运行
@@To1:
movdqa xmm0, DQWORD PTR [eax] // 1 uop
pcmpeqw xmm0, DQWORD PTR [eax+edx] // 2 uops on Intel SnB/IvB, 1 on Nehalem and earlier or Haswell and later.
pmovmskb ebx, xmm0 // 1 uop
cmp ebx, 65535
jne @@Final // 1 uop (macro-fused with cmp)
add eax, 16 // 1 uop
sub ecx, 8
{ AnsiChar : sub ecx, 16 }
ja @@To1 // 1 uop (macro-fused with sub on SnB and later, otherwise 2)
Run Code Online (Sandbox Code Playgroud)
这是7个融合域uops,因此它只能在主流Intel CPU的每次迭代中以最佳7/4周期从前端发出.这远远不是每个时钟2个负载的瓶颈.在Haswell和之后,每次迭代它是6/4个周期,因为索引寻址模式可以与2操作数加载修改指令保持微融合pcmpeqw,但不是其他任何东西(如pabsw xmm0, [eax+edx](不读取目标)或AVX vpcmpeqw xmm0, xmm0, [eax+edx](3个操作数) )).请参阅微融合和寻址模式.
对于具有更好设置/清理的小字符串,这可能更有效.
在指针设置代码中,cmp如果首先检查NULL指针,则可以保存.您可以sub/ jne减去并检查两者是否与相同的宏融合比较和分支相等.(它只会在英特尔Sandybridge系列上进行宏观融合,只有Haswell可以在一个解码模块中进行2次宏观融合.但是Haswell/Broadwell/Skylake CPU很常见并且变得越来越常见,这对其他产品没有任何不利影响. CPU,除非等指针是如此常见,以至于首先进行检查很重要.)
在返回路径中:尽可能使用xor eax,eax零寄存器,而不是mov eax, 0.
你似乎没有避免从字符串末尾读取.您应该使用最终位于页面末尾的字符串来测试您的函数,其中下一页是未映射的.
xor ebx, [eax + edx]cmp与早期标量测试相比,它没有任何优势. cmp/jnz可以与jcc宏观融合,但xor不能.
您加载一个掩码来处理清理,以覆盖您读取字符串末尾的情况.你可能仍然可以使用通常的bsf方法来找到位图中的第一个区别.我想反过来not找到第一个不比较相等的位置,并检查它是否小于剩余的字符串长度.
或者你可以生成与运行中的面具mov eax, -1和shr,我想.或者为了加载它,你有时可以使用滑动窗口进入...,0,0,0,-1,-1,-1,...数组,但是你需要子字节偏移,这样就不起作用了.(它适用于矢量蒙版,如果你想掩盖和重做pmovmskb. 使用未对齐的缓冲区进行矢量化:使用VMASKMOVPS:从未对齐计数生成掩码?或者根本不使用该insn).
你的方式也不错,只要它没有缓存错过.我可能会动态生成面具.也许在另一个寄存器的循环之前,因为你可以掩码得到count % 8,所以掩码生成可以与循环并行发生.
| 归档时间: |
|
| 查看次数: |
1374 次 |
| 最近记录: |