performance - 检查两个 SSE 寄存器是否均不为零而不破坏它们

标签 performance optimization assembly sse simd

我想测试两个SSE寄存器是否都为零而不破坏它们。

这是我目前拥有的代码:

uint8_t *src;  // Assume it is initialized and 16-byte aligned
__m128i xmm0, xmm1, xmm2;

xmm0 = _mm_load_si128((__m128i const*)&src[i]); // Need to preserve xmm0 & xmm1
xmm1 = _mm_load_si128((__m128i const*)&src[i+16]);
xmm2 = _mm_or_si128(xmm0, xmm1);
if (!_mm_testz_si128(xmm2, xmm2)) { // Test both are not zero
}

这是最好的方法(最高使用 SSE 4.2)吗?

最佳答案

我从这个问题中学到了一些有用的东西。让我们首先看一些标量代码

extern foo2(int x, int y);
void foo(int x, int y) {
    if((x || y)!=0) foo2(x,y);
}

像这样编译gcc -O3 -S -masm=intel test.c,重要的程序集是

 mov       eax, edi   ; edi = x, esi = y -> copy x into eax
 or        eax, esi   ; eax = x | y and set zero flag in FLAGS if zero
 jne       .L4        ; jump not zero

现在让我们看看如何测试 SIMD 寄存器是否为零。与标量代码不同,没有 SIMD FLAGS 寄存器。然而,在SSE4.1中,有SIMD测试指令可以在标量FLAGS寄存器中设置零标志(和进位标志)。

extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
    __m128i z = _mm_or_si128(x,y);
    if (!_mm_testz_si128(z,z)) foo2(x,y);
}

使用c99 -msse4.1 -O3 -masm=intel -S test_SSE.c编译,重要的程序集为

movdqa      xmm2, xmm0 ; xmm0 = x, xmm1 = y, copy x into xmm2
por         xmm2, xmm1 ; xmm2 = x | y
ptest       xmm2, xmm2 ; set zero flag if zero
jne         .L4        ; jump not zero 

请注意,这还需要一条指令,因为打包的按位或不会设置零标志。另请注意,标量版本和 SIMD 版本都需要使用额外的寄存器(标量情况下为 eax,SIMD 情况下为 xmm2)。 因此,为了回答您的问题,您当前的解决方案是您能做的最好的解决方案。

但是,如果您没有 SSE4.1 或更高版本的处理器,则必须使用 _mm_movemask_epi8 另一种仅需要 SSE2 的替代方案是使用 _mm_movemask_epi8

extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
    if (_mm_movemask_epi8(_mm_or_si128(x,y))) foo2(x,y);   
}

重要的组件是

movdqa      xmm2, xmm0
por         xmm2, xmm1
pmovmskb    eax, xmm2
test        eax, eax
jne         .L4

请注意,与 SSE4.1 ptest 指令相比,这还需要一条指令。

到目前为止,我一直在使用 pmovmaskb 指令,因为 Sandy Bridge 之前的处理器上的延迟比 ptest 更好。然而,我在 Haswell 之前就意识到了这一点。在 Haswell 上,pmovmaskb 的延迟比 ptest 的延迟更差。它们都有相同的吞吐量。但在这种情况下,这并不重要。重要的是(我之前没有意识到)pmovmaskb 没有设置 FLAGS 寄存器,因此它需要另一条指令。 所以现在我将在我的关键循环中使用 ptest谢谢您的提问。

编辑:根据OP的建议,有一种方法可以在不使用另一个SSE寄存器的情况下完成此操作。

extern foo2(__m128i x, __m128i y);
void foo(__m128i x, __m128i y) {
    if (_mm_movemask_epi8(x) | _mm_movemask_epi8(y)) foo2(x,y);    
}

GCC 的相关程序集是:

pmovmskb    eax, xmm0
pmovmskb    edx, xmm1
or          edx, eax
jne         .L4

它不使用另一个 xmm 寄存器,而是使用两个标量寄存器。

请注意,更少的指令并不一定意味着更好的性能。这些解决方案中哪一个最好?您必须对它们中的每一个进行测试才能找到答案。

关于performance - 检查两个 SSE 寄存器是否均不为零而不破坏它们,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26623050/

相关文章:

database - ArangoDB 通过 _id 获取文档,每个查询缓存

java - 比构造函数快?

python - Pandas 中非唯一索引的性能影响是什么?

c++ - 优化 std::visit 可能吗?

arrays - Powershell-创建大量哈希表的最快方法

java - AWT 文本测量实现

assembly - 在不同处理器(x86程序集)上运行代码

c# - String.Format 和垃圾回收

c - 有没有办法知道全局变量和静态变量驻留在数据段(.data + .bss)内的位置?

testing - 如何测试引导加载程序