Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/158.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/62.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 如何优化长系列If/then条件表达式-SIMD_C++_C_Optimization_Conditional_Simd - Fatal编程技术网

C++ 如何优化长系列If/then条件表达式-SIMD

C++ 如何优化长系列If/then条件表达式-SIMD,c++,c,optimization,conditional,simd,C++,C,Optimization,Conditional,Simd,我正在使用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)

我正在使用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++编译器支持的英特尔内部函数会降低处理速度


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

您可以尝试完全消除条件,然后再次测量时间。 你的代码

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
    ))))))));

或许,您可以进一步优化此代码,了解变量的可能内容。

您可以尝试完全消除条件并再次测量时间。 你的代码

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
    ))))))));

或许,您可以进一步优化此代码,了解变量的可能内容。

我假设:

处理int32数据时,可以很容易地将其更改为float32。 您可以一次向函数传递4个值,而不仅仅是一个值。这就是人们通常所说的矢量化。 对常数进行排序,即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个压缩值。 然后,我们将其与所有8个具有_mm_cmp*内在性质的常数进行比较。此比较产生8位掩码cmp_pX、cmp_mX。在位掩码中,与数字对应的所有位要么为零,要么为一。每个比较设置32个零位,这是错误的。如果比较条件为真,则32位设置为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

我假设有几件事:

处理int32数据时,可以很容易地将其更改为float32。 您可以一次向函数传递4个值,而不仅仅是一个值。这就是人们通常所说的矢量化。 对常数进行排序,即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个压缩值。 然后,我们将其与所有8个具有_mm_cmp*内在性质的常数进行比较。此比较产生8位掩码cmp_pX、cmp_mX。在位掩码中,与数字对应的所有位要么为零,要么为一。每个比较设置32个零位,这是错误的。如果比较条件为真,则32位设置为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

@布莱克我认为这实际上是少数几个可以适用于两种语言的问题之一。。。答案可能对两者都有效。T1、T2、T3和NEAR是如何定义的?您使用了哪些英特尔内部函数?你能展示那个实验的代码吗?如果T[n]s的顺序是固定的,那么二进制搜索可能比线性遍历条件更快。@black Im使用MSVC2010编译器。IMHO,这与C或C++@black都相关,我认为这实际上是少数几个适用于两种语言的问题之一。。。答案可能对两者都有效。T1、T2、T3和NEAR是如何定义的?您使用了哪些英特尔内部函数?你能展示那个实验的代码吗?如果T[n]s的顺序是固定的,那么二进制搜索可能比线性遍历条件更快。@black Im使用MSVC2010编译器。这与C或C++都相关是的,你的假设是正确的。您的解决方案在我的机器上运行速度快2倍。非常感谢。如果您的目标CPU具有水平加法或快速洗牌功能,那么只需对每4个字进行一次比较,将结果右移31,乘以输出值,然后水平相加,可能会更快。不过,这还是一个很好的解决方案。@Damon:你的建议可能适用于单输入f
函数,但我认为当我们处理压缩输入时,水平操作是没有意义的。是的,你的假设是正确的。您的解决方案在我的机器上运行速度快2倍。非常感谢。如果您的目标CPU具有水平加法或快速洗牌功能,那么只需对每4个字进行一次比较,将结果右移31,乘以输出值,然后水平相加,可能会更快。不过,这还是一个很好的解决方案。@Damon:你的建议可能适用于单输入函数,但我认为在处理压缩输入时,水平操作毫无意义。