C语言 循环中if(循环不变)if语句的优化

rkttyhzu  于 11个月前  发布在  其他
关注(0)|答案(5)|浏览(141)

我在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在函数中是一个常量,好的编译器会优化循环,使分支不会一直拖慢速度吗?
1.如果没有,那么优化这段代码的好方法是什么?

**编辑:**这里是一个更新,有一个更完整的例子:

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循环变得更简单,希望更容易向量化!(同时也避免了无数次的额外逻辑)

oymdgrw7

oymdgrw71#

优化代码的一个显而易见的方法是将条件拉到循环外:

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[]赋值。

lstz6jyr

lstz6jyr2#

微优化

通常他们不值得花时间-审查更大的问题更有效。
然而,微观优化,尝试各种方法,然后分析它们以找到最好的方法可以做出适度的改进。
除了@wallyk和@kabanus很好的答案,一些简单的编译器还受益于以0结尾的循环。

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

字符串
[edit第二次优化]
OP增加了一个更复杂的例子。其中一个问题是编译器不能假设b和其他内存指向的内存不重叠。这阻止了某些优化。
假设它们实际上不重叠,在b上使用restrict来允许优化。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) {

lskq00tm

lskq00tm3#

你所有的命令都是快速的O(1)循环命令。if绝对是优化的,如果你所有的命令都是r[i]=somethingquick的话,你的for+if也是优化的。问题可能归结为你的大int能有多小?
一个快速的int main,从INT_MININT_MAX求和成一个长变量,对我来说在Windows上的Ubuntu子系统上需要大约10秒。你的命令可能会乘以几个,很快就到了一分钟。底线是,如果你真的迭代了一吨,这可能是不可避免的。
如果r[i]是独立计算的,这将是线程/多处理的经典用法。
编辑:
我认为%已经被编译器优化了,但如果没有,请注意x & 1对于奇/偶检查来说要快得多。

ifsvaxew

ifsvaxew4#

假设是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   
}

字符串

ffx8fchx

ffx8fchx5#

尽管if语句在任何像样的编译器上都会被优化掉(除非你要求编译器不要优化),但我会考虑把优化写进去(以防你编译时没有优化)。
此外,尽管编译器可能会优化“绝对”if语句,但我会考虑手动优化它,使用任何可用的内置或using bitwise操作。

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

字符串
这将取popcount的最后一位(1 ==奇数,0 ==偶数),将其乘以const(如果是奇数,则所有位为1,如果为真,则所有位为0),然后对c[I]值进行XOR(与0-c[I]~(c[I])相同)。
这将避免在第二个absolute if语句未优化的情况下出现指令跳转。
P.S.
我使用了一个8字节长的值,并通过将其转换为int来截断它的长度。这是因为我不知道int在您的系统上可能有多长(在我的系统上是4字节,即0xFFFFFFFF)。

相关问题