【发布时间】:2012-11-09 18:11:30
【问题描述】:
我正在尝试将以下代码转换为 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 放入该插槽),那么所有比较的结果如何(例如)and-ed or 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 寄存器,否则该寄存器会被掩码占用。
【问题讨论】:
标签: c optimization assembly sse avx