【发布时间】:2020-07-29 10:35:28
【问题描述】:
这是我做错了但我还不完全理解的那些 n00b 问题之一。
xxhash32 算法有一个不错的 16 字节内部循环,可以使用 SIMD 加快速度,因此,作为我自己的练习,这就是我想要做的。
循环体如下所示(numBytes 是 16 的倍数):
// C# that gets auto-vectorized. uint4 is a vector of 4 elements
uint4 state = new uint4(Prime1 + Prime2, Prime2, 0, (uint)-Prime1) + seed;
int count = numBytes >> 4;
for (int i = 0; i < count; ++i) {
state += *p++ * Prime2;
state = (state << 13) | (state >> 19);
state *= Prime1;
}
hash = rol(state.x, 1) + rol(state.y, 7) + rol(state.z, 12) + rol(state.w, 18);
我已将其翻译成以下 SSE2/SSE4.1 内在函数:
auto prime1 = _mm_set1_epi32(kPrime1);
auto prime2 = _mm_set1_epi32(kPrime2);
auto state = _mm_set_epi32(seed + kPrime1 + kPrime2, seed + kPrime2, seed, seed - kPrime1);
int32_t count = size >> 4; // =/16
for (int32_t i = 0; i < count; i++) {
state = _mm_add_epi32(state, _mm_mullo_epi32(_mm_loadu_si128(p128++), prime2));
state = _mm_or_si128(_mm_sll_epi32(state, _mm_cvtsi32_si128(13)), _mm_srl_epi32(state, _mm_cvtsi32_si128(19)));
state = _mm_mullo_epi32(state, prime1);
}
uint32_t temp[4];
_mm_storeu_si128(state, temp);
hash = _lrotl(temp[0], 1) + _lrotl(temp[1], 7) + _lrotl(temp[2], 12) + _lrotl(temp[3], 18);
下面是内循环体的拆解:
mov rax,qword ptr [p128]
mov qword ptr [rsp+88h],rax
mov rax,qword ptr [rsp+88h]
movdqu xmm0,xmmword ptr [rax]
movdqa xmmword ptr [rsp+90h],xmm0
movdqa xmm0,xmmword ptr [rsp+90h]
movdqa xmmword ptr [rsp+120h],xmm0
mov rax,qword ptr [p128]
add rax,10h
mov qword ptr [p128],rax
movdqa xmm0,xmmword ptr [prime2]
movdqa xmmword ptr [rsp+140h],xmm0
movdqa xmm0,xmmword ptr [rsp+120h]
movdqa xmmword ptr [rsp+130h],xmm0
movdqa xmm0,xmmword ptr [rsp+130h]
pmulld xmm0,xmmword ptr [rsp+140h]
movdqa xmmword ptr [rsp+150h],xmm0
movdqa xmm0,xmmword ptr [rsp+150h]
movdqa xmmword ptr [rsp+160h],xmm0
movdqa xmm0,xmmword ptr [rsp+160h]
movdqa xmmword ptr [rsp+170h],xmm0
movdqa xmm0,xmmword ptr [rsp+20h]
movdqa xmmword ptr [rsp+100h],xmm0
movdqa xmm0,xmmword ptr [rsp+100h]
paddd xmm0,xmmword ptr [rsp+170h]
movdqa xmmword ptr [rsp+180h],xmm0
movdqa xmm0,xmmword ptr [rsp+180h]
movdqa xmmword ptr [rsp+190h],xmm0
movdqa xmm0,xmmword ptr [rsp+190h]
movdqa xmmword ptr [rsp+20h],xmm0
movdqa xmm0,xmmword ptr [rsp+20h]
movdqa xmmword ptr [rsp+1A0h],xmm0
mov eax,13h
movd xmm0,eax
movdqa xmmword ptr [rsp+1B0h],xmm0
movdqa xmm0,xmmword ptr [rsp+1A0h]
psrld xmm0,xmmword ptr [rsp+1B0h]
movdqa xmmword ptr [rsp+1C0h],xmm0
movdqa xmm0,xmmword ptr [rsp+1C0h]
movdqa xmmword ptr [rsp+200h],xmm0
movdqa xmm0,xmmword ptr [rsp+20h]
movdqa xmmword ptr [rsp+1D0h],xmm0
mov eax,0Dh
movd xmm0,eax
movdqa xmmword ptr [rsp+1E0h],xmm0
movdqa xmm0,xmmword ptr [rsp+1D0h]
pslld xmm0,xmmword ptr [rsp+1E0h]
movdqa xmmword ptr [rsp+1F0h],xmm0
movdqa xmm0,xmmword ptr [rsp+1F0h]
movdqa xmmword ptr [rsp+210h],xmm0
movdqa xmm0,xmmword ptr [rsp+200h]
movdqa xmmword ptr [rsp+230h],xmm0
movdqa xmm0,xmmword ptr [rsp+210h]
movdqa xmmword ptr [rsp+220h],xmm0
movdqa xmm0,xmmword ptr [rsp+220h]
por xmm0,xmmword ptr [rsp+230h]
movdqa xmmword ptr [rsp+240h],xmm0
movdqa xmm0,xmmword ptr [rsp+240h]
movdqa xmmword ptr [rsp+250h],xmm0
movdqa xmm0,xmmword ptr [rsp+250h]
movdqa xmmword ptr [rsp+20h],xmm0
movdqa xmm0,xmmword ptr [prime1]
movdqa xmmword ptr [rsp+280h],xmm0
movdqa xmm0,xmmword ptr [rsp+20h]
movdqa xmmword ptr [rsp+270h],xmm0
movdqa xmm0,xmmword ptr [rsp+270h]
pmulld xmm0,xmmword ptr [rsp+280h]
movdqa xmmword ptr [rsp+290h],xmm0
movdqa xmm0,xmmword ptr [rsp+290h]
movdqa xmmword ptr [rsp+2A0h],xmm0
movdqa xmm0,xmmword ptr [rsp+2A0h]
movdqa xmmword ptr [rsp+20h],xmm0
关于反汇编的一些问题:
- 为什么会有这么多
movdqa指令(我认为内在函数的目的是它们映射到特定的硬件指令。)? - 为什么只使用了
xmm0,在我看来它正在将内存移入和移出向量管道(我希望使用更多的 xmmN 寄存器)?
这是用 Visual C++ 2017 编译的,我没有启用额外的优化。
当我在一个 64 MiB 的块上运行这两个 sn-ps 时,多次运行,标量代码大约快 3 个计时器。这不是我所期望的,我错过了什么?
【问题讨论】:
-
_mm_sll_epi32带有向量 13 可能是性能问题的一部分(一旦启用优化)。使用_mm_slli_epi32,它使用直接形式pslld xmm, 13。 MSVC 对内在函数非常直白,不会为您进行此优化。额外的移位 uops 可能正在竞争_mm_mullo_epi32需要的一些相同的执行端口 - 不幸的是它很慢。 -
IDK 如果值得使用 SIMD 来尝试模拟 4x
_lrotl。也许只有当您使用 AVX2 进行可变计数班次时。你确定你的循环计数是正确的,并且你没有在 SIMD 版本中做 4 倍的工作吗?两者都使用size >> 4;,但是每个向量做4个元素应该意味着你只做1/4的迭代。 -
哦,等等,两个循环都像 SIMD 一样编写。编译器可以自动向量化第一个。但第一个看起来甚至不像 C++。
uint4 state = new ...不会编译,除非uint4实际上是指针类型的 typedef。也许那是C#?如果您想讨论速度比较,最好用评论指出不同的语言。 -
@PeterCordes 是的,第一个示例是 Unity 的 C# 参考实现,当使用 Burst 编译器基础结构编译时,它会进行自动矢量化。我正在尝试更多地了解这是如何发生的,并且我想自己编写一些 SIMD。
-
那么您应该将由此生成的 asm 与您从手动矢量化中获得的结果进行比较。
标签: visual-c++ sse simd intrinsics