C中if语句的编译器优化

时间:2017-10-06 23:06:05

标签: c optimization compiler-optimization

我在C中有这样的函数(在伪代码中,丢弃不重要的部分):

int func(int s, int x, int* a, int* r) {
    int i;

    // do some stuff

    for (i=0;i<a_really_big_int;++i) {
        if (s) r[i] = x ^ i;
        else r[i] = x ^ a[i];
        // and maybe a couple other ways of computing r
        // that are equally fast individually
    }

    // do some other stuff

}

这个代码被调用得如此之多,以至于这个循环实际上是代码中的速度瓶颈。我想知道几件事:

  1. 由于开关s是函数中的常量,好的编译器会优化循环,以便分支不会一直减慢速度吗?

  2. 如果没有,优化此代码的好方法是什么?

  3. ====

    以下是更新的更新示例:

    int func(int s,
             int start,int stop,int stride,
             double *x,double *b,
             int *a,int *flips,int *signs,int i_max,
             double *c)
    {
      int i,k,st;
      for (k=start; k<stop; k += stride) {
        b[k] = 0;
        for (i=0;i<i_max;++i) {
    
          /* this is the code in question */
          if (s) st = k^flips[i];
          else st = a[k]^flips[i];
          /* done with code in question */
    
          b[k] += x[st] * (__builtin_popcount(st & signs[i])%2 ? -c[i] : c[i]);
        }
      }
    }
    

    编辑2:

    如果有人好奇,我最终重构代码并将整个内部for循环(使用i_max)提升到外部,使really_big_int循环更加简单,并且希望很容易进行矢量化! (并且还避免多次使用额外的逻辑)

5 个答案:

答案 0 :(得分:4)

优化代码的一种显而易见的方法是在循环外拉出条件:

if (s)
    for (i=0;i<a_really_big_int;++i) {
        r[i] = x ^ i;
    }
else
    for (i=0;i<a_really_big_int;++i) {
        r[i] = x ^ a[i];
    }

精明的编译器可能一次可以将其更改为多个元素的r []赋值。

答案 1 :(得分:2)

<强>微优化

通常他们不值得花时间 - 审查更大的问题更有效。

然而,微观优化,尝试各种方法,然后分析它们以找到最佳方法可以做出适度的改进。

除了@wallyk@kabanus精确答案之外,一些简单的编译器还可以使用以0结尾的循环。

// for (i=0;i<a_really_big_int;++i) {
for (i=a_really_big_int; --i; ) {

[编辑第2次优化]

OP增加了一个更具竞争力的例子。其中一个问题是编译器不能假设b和其他人指向的内存不重叠。这可以防止某些优化。

假设它们实际上不重叠,请在restrict上使用b以允许优化。 const对于那些没有推断出来的弱代编译器也有帮助。如果参考数据不重叠,其他人restrict也可能会受益。

// int func(int s, int start, int stop, int stride, double *x,
//     double *b, int *a, int *flips,
//     int *signs, int i_max, double *c) {

int func(int s, int start, int stop, int stride, const double * restrict x,
    double * restrict b, const int * restrict a, const int * restrict flips, 
    const int * restrict signs, int i_max, double *c) {

答案 2 :(得分:1)

所有命令都是循环中的快速O(1)命令。 if绝对是优化的,如果您的所有命令都是r[i]=somethingquick形式,那么你的for +也是如此。问题可能归结为你对大小的影响有多大?

int mainINT_MIN的快速INT_MAX总结为一个长变量,在Windows上的Ubuntu子系统上需要大约10秒钟。您的命令可能会乘以这几个,这很快就会达到一分钟。最重要的是,如果你真的在迭代一吨,这可能是不可避免的。

如果r[i]是独立计算的,这将是线程/多处理的经典用法。

编辑:

我认为%无论如何都会被编译器优化,但如果没有,请注意x & 1对奇数/偶数检查要快得多。

答案 3 :(得分:1)

假设x86_64,您可以确保指针对齐到16个字节并使用intrinsics。如果它仅在具有AVX2的系统上运行,则可以使用__mm256变体(类似于avx512 *)

int func(int s, int x, const __m128i* restrict a, __m128i* restrict r) {
    size_t i = 0, max = a_really_big_int / 4;
    __m128i xv =  _mm_set1_epi32(x);
    // do some stuff
    if (s) {
        __m128i iv = _mm_set_epi32(3,2,1,0); //or is it 0,1,2,3?
        __m128i four = _mm_set1_epi32(4);
        for ( ;i<max; ++i, iv=_mm_add_epi32(iv,four)) {
            r[i] = _mm_xor_si128(xv,iv);
        }
    }else{ /*not (s)*/
        for (;i<max;++i){
            r[i] = _mm_xor_si128(xv,a[i]);
        }
    }
    // do some other stuff   
}

答案 4 :(得分:0)

尽管if语句将在任何体面的编译器上进行优化(除非您要求编译器不进行优化),但我会考虑编写优化(以防您在没有优化的情况下进行编译)。

此外,虽然编译器可能会优化&#34;绝对&#34; if声明,我会考虑使用任何可用的内置或using bitwise操作手动优化它。

b[k] += x[st] *
        ( ((__builtin_popcount(st & signs[I]) & 1) *
           ((int)0xFFFFFFFFFFFFFFFF)) ^c[I] );

这将取popcount的最后一位(1 ==奇数,0 ==偶数),乘以const(所有位1如果是奇数,所有位0如果为真)并且比XOR { {1}}值(与c[I]0-c[I]相同。

这将避免在第二个~(c[I]) if语句未被优化的情况下跳转指令。

P.S。

我使用了一个8字节长的值,并通过将其转换为absolute来截断它的长度。这是因为我不知道你的系统int可能有多长(我的4个字节,int)。