如何在SSE中有效地结合比较?

时间:2012-11-21 13:18:23

标签: c optimization assembly sse avx

我正在尝试将以下代码转换为SSE ​​/ AVX:

float x1, x2, x3;
float a1[], a2[], a3[], b1[], b2[], b3[];
for (i=0; i < N; i++)
{
    if (x1 > a1[i] && x2 > a2[i] && x3 > a3[i] && x1 < b1[i] && x2 < b2[i] && x3 < b3[i])
    {
        // do something with i
    }
}

这里N是一个小常量,假设是8. if(...)语句在大多数情况下的计算结果为假。

首次尝试:

__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0 
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0

for (int i = 0; i < N; i++)
{
    __m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
    __m128 lt_mask = _mm_cmplt_ps(x, b[i]);
    __m128 mask = _mm_and_ps(gt_mask, lt_mask);
    if (_mm_movemask_epi8 (_mm_castps_si128(mask)) == 0xfff0)
    {
        // do something with i
    }
}

这很有效,并且速度相当快。问题是,是否有更有效的方法来做到这一点?特别是,如果有一个寄存器包含来自浮点数的SSE或AVX比较的结果(将0xffff0x0000放在该插槽中),那么所有比较的结果如何(例如)和一般来说还是一起订购? PMOVMSKB(或相应的_mm_movemask内在的)是标准的方法吗?

另外,如何在上面的代码中使用AVX 256位寄存器代替SSE?

编辑:

使用VPTEST(来自_mm_test * intrinsic)对版本进行测试和基准测试,如下所示。

__m128 x; // x1, x2, x3, 0
__m128 a[N]; // packed a1[i], a2[i], a3[i], 0
__m128 b[N]; // packed b1[i], b2[i], b3[i], 0
__m128i ref_mask = _mm_set_epi32(0xffff, 0xffff, 0xffff, 0x0000);

for (int i = 0; i < N; i++)
{
    __m128 gt_mask = _mm_cmpgt_ps(x, a[i]);
    __m128 lt_mask = _mm_cmplt_ps(x, b[i]);
    __m128 mask = _mm_and_ps(gt_mask, lt_mask);
    if (_mm_testc_si128(_mm_castps_si128(mask), ref_mask))
    {
        // do stuff with i
    }
}

这也有效,而且速度很快。对此进行基准测试(Intel i7-2630QM,Windows 7,cygwin 1.7,cygwin gcc 4.5.3或mingw x86_64 gcc 4.5.3,N = 8)显示这与64bit上面的代码(小于0.1%)相同。内部循环的任何一个版本在数据上平均运行大约6.8个时钟,这些数据全部在缓存中,并且比较返回总是假的。

有趣的是,在32位上,_mm_test版本运行速度慢了约10%。事实证明,编译器在循环展开后溢出掩码并且必须重新读回它们;这可能是不必要的,可以在手工编码的装配中避免。

选择哪种方法?似乎没有令人信服的理由更喜欢VPTEST而不是VMOVMSKPS。实际上,有一个轻微的理由偏好VMOVMSKPS,即它释放了一个xmm寄存器,否则它将由掩码占用。

2 个答案:

答案 0 :(得分:12)

如果您正在使用浮点数,通常需要使用MOVMSKPS(和相应的AVX指令VMOVMSKPS)而不是PMOVMSKB

除此之外,是的,这是这样做的一种标准方式;您还可以使用PTESTVPTEST)根据SSE或AVX AND或ANDNOT的结果直接更新条件标志。

答案 1 :(得分:2)

解决您的编辑版本:

如果您要直接分支PTEST的结果,使用它的速度要快于MOVMSKPS到GP reg的速度,然后执行{{ 1}}用于设置分支指令的标志。在AMD CPU上,在向量和整数域之间移动数据非常慢(5到10个周期的延迟,具体取决于CPU型号)。

至于需要TEST的额外注册,您通常不会。您可以使用与两个args相同的值,就像使用常规非向量PTEST指令一样。 (测试TEST与测试foo & foo)相同。

在您的情况下,您需要检查是否已设置所有向量元素。如果你颠倒了比较,然后将结果与OR一起(所以你正在测试foo)你需要测试所有零的向量,而不是所有的向量。但处理低元素仍然存在问题。如果您需要保存寄存器以避免需要!(x1 < a1[i]) || !(x2 < a2[i]) || ... / PTEST的矢量掩码,则可以在执行VTESTPS之前将向量右移4个字节并将其分支为全部-Zero。

AVX介绍PTEST,我想这可以避免浮动 - &gt; int旁路延迟。但是,如果您使用任何int-domain指令为测试生成输入,那么您也可以使用VTESTPS。 (我知道你使用的是内在函数,但与助记符相比,它们很难打字和查看。)