我正在尝试将以下代码转换为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比较的结果(将0xffff
或0x0000
放在该插槽中),那么所有比较的结果如何(例如)和一般来说还是一起订购? 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寄存器,否则它将由掩码占用。
答案 0 :(得分:12)
如果您正在使用浮点数,通常需要使用MOVMSKPS
(和相应的AVX指令VMOVMSKPS
)而不是PMOVMSKB
。
除此之外,是的,这是这样做的一种标准方式;您还可以使用PTEST
(VPTEST
)根据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
。 (我知道你使用的是内在函数,但与助记符相比,它们很难打字和查看。)