如何优化If/then条件表达式的长序列- SIMD

How to optimization long series of If/then conditional expressions - SIMD

本文关键字:表达式 SIMD 条件 then 何优化 优化 If      更新时间:2023-10-16

我使用SIMD来提高C代码的性能,但我遇到了一个函数,其中有许多if/then条件,如下所示:

if (Di <= -T3) return  -4;
if (Di <= -T2) return  -3;
if (Di <= -T1) return  -2;
if (Di < -NEAR)  return  -1;
if (Di <=  NEAR) return   0;
if (Di < T1)   return   1;
if (Di < T2)   return   2;
if (Di < T3)   return   3;
return  4;

使用vc++编译器支持的Intel内部函数,处理时间较慢。

那么,有没有更好的方法来优化这一长串条件表达式呢?

我假设了以下几点:

  1. 你处理int32数据(它可以很容易地改变为float32,虽然)。
  2. 你可以一次传递4个值给你的函数(不只是一个)。这就是人们通常所说的向量化
  3. 常数排序,即0

这是一个向量化函数:

__m128i func4(__m128i D) {
  __m128i cmp_m3 = _mm_cmpgt_epi32(D, _mm_set1_epi32(-T3));
  __m128i cmp_m2 = _mm_cmpgt_epi32(D, _mm_set1_epi32(-T2));
  __m128i cmp_m1 = _mm_cmpgt_epi32(D, _mm_set1_epi32(-T1));
  __m128i cmp_p0 = _mm_cmpgt_epi32(D, _mm_set1_epi32(NEAR));
  __m128i reduce_true = _mm_add_epi32(_mm_add_epi32(cmp_m3, cmp_m2), _mm_add_epi32(cmp_m1, cmp_p0));
  __m128i cmp_m0 = _mm_cmplt_epi32(D, _mm_set1_epi32(-NEAR));
  __m128i cmp_p1 = _mm_cmplt_epi32(D, _mm_set1_epi32(T1));
  __m128i cmp_p2 = _mm_cmplt_epi32(D, _mm_set1_epi32(T2));
  __m128i cmp_p3 = _mm_cmplt_epi32(D, _mm_set1_epi32(T3));
  __m128i reduce_false = _mm_add_epi32(_mm_add_epi32(cmp_p3, cmp_p2), _mm_add_epi32(cmp_p1, cmp_m0));
  return _mm_sub_epi32(reduce_false, reduce_true);
}

如果输入数据是随机的,那么它的工作速度比MSVC2013 x64的Ivy Bridge上的原始版本快11倍:

Time = 4.436   (-39910000)
Time = 0.409   (-39910000)

完整的测试代码在这里。

这个想法相当简单。您可以在上面链接的函数funcX中看到建议解决方案的非矢量化版本。它可能比语言更能解释一切。

我们将寄存器D作为输入,它包含4个打包值。然后我们把它和_mm_cmp*固有的8个常数进行比较。这个比较产生了8个位掩码cmp_pX, cmp_mX。在位掩码中,与一个数字相对应的所有位不是0就是1。每次比较都设置32个0位,这是假的。如果比较条件为真,则32位设置为1。

现在回想一下,所有位都为1的32位整数在有符号表示中是-1。当我们把四个比较结果加在一起时,我们得到一组计数是否定的。最后,取两个计数的差,即为期望的结果。

注:下面是为内循环生成的汇编代码:

movdqa  xmm3, XMMWORD PTR [rcx]
movdqa  xmm4, xmm10
movdqa  xmm0, xmm9
add rcx, 16
pcmpgtd xmm0, xmm3
pcmpgtd xmm4, xmm3
paddd   xmm4, xmm0
movdqa  xmm2, xmm3
movdqa  xmm1, xmm8
pcmpgtd xmm1, xmm3
pcmpgtd xmm2, xmm14
movdqa  xmm0, xmm7
pcmpgtd xmm0, xmm3
paddd   xmm1, xmm0
paddd   xmm4, xmm1
movdqa  xmm0, xmm3
movdqa  xmm1, xmm3
pcmpgtd xmm1, xmm12
pcmpgtd xmm0, xmm13
pcmpgtd xmm3, xmm11
paddd   xmm1, xmm3
paddd   xmm2, xmm0
paddd   xmm2, xmm1
psubd   xmm4, xmm2
paddd   xmm4, xmm5
movdqa  xmm5, xmm4
cmp rcx, r15
jl  SHORT $LL3@main

您可以尝试摆脱所有条件并重新测量时间。代码

if (Di <= -T3) return  -4;
if (Di <= -T2) return  -3;
if (Di <= -T1) return  -2;
if (Di < -NEAR)  return  -1;
if (Di <=  NEAR) return   0;
if (Di < T1)   return   1;
if (Di < T2)   return   2;
if (Di < T3)   return   3;
return  4;

可以转换为无条件形式:

return
    (Di <= -T3)*(-4) + (Di > -T3) * (
    (Di <= -T2)*(-3) + (Di > -T2) * (
    (Di <= -T1)*(-2) + (Di > -T1) * (
    (Di < -NEAR)*(-1) + (Di >= -NEAR) * (
    (Di <=  NEAR)*0 + (Di > NEAR) * (
    (Di < T1)*1 + (Di >= T1) * (
    (Di < T2)*2 + (Di >= T2) * (
    (Di < T3)*3 + (Di >= T3) * (
    4
    ))))))));

也许,您可以进一步优化这段代码,对变量的可能内容有一些了解。