我正在尝试将以下代码转换为 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(...) 语句在大多数情况下的计算结果为 false。
第一次尝试:
__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
放入该插槽)的结果,那么所有比较的结果如何(例如)与 -ed 或 or-ed 在一起,一般来说? PMOVMSKB
(或相应的内在)是_mm_movemask
执行此操作的标准方法吗?
另外,如何在上面的代码中使用 AVX 256 位寄存器而不是 SSE?
编辑:
使用 VPTEST(来自 _mm_test* 内在)对版本进行测试和基准测试,如下所示。
__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)进行基准测试表明,这与 64 位上的上述代码速度相同(小于 0.1%) . 内部循环的任一版本平均运行大约 6.8 个时钟,这些数据都在缓存中,并且比较总是返回 false。
有趣的是,在 32 位上,_mm_test 版本的运行速度慢了大约 10%。事实证明,编译器在循环展开后会溢出掩码,并且必须重新读取它们;这可能是不必要的,并且可以在手动编码组装中避免。
选择哪种方法?似乎没有令人信服的理由更VPTEST
喜欢VMOVMSKPS
. 实际上,有一个更喜欢的理由VMOVMSKPS
,即它释放了一个 xmm 寄存器,否则该寄存器会被掩码占用。