C 当限制为959而不是960时,为什么要优化简单循环?

C 当限制为959而不是960时,为什么要优化简单循环?,c,gcc,optimization,clang,C,Gcc,Optimization,Clang,考虑这个简单的循环: float f(float x[]) { float p = 1.0; for (int i = 0; i < 959; i++) p += 1; return p; } 换句话说,它只是将答案设置为960而没有循环 但是,如果将代码更改为: float f(float x[]) { float p = 1.0; for (int i = 0; i < 960; i++) p += 1; return p; } 为什么

考虑这个简单的循环:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 959; i++)
    p += 1;
  return p;
}
换句话说,它只是将答案设置为960而没有循环

但是,如果将代码更改为:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 960; i++)
    p += 1;
  return p;
}
为什么这和为什么clang和gcc完全一样


如果将
float
替换为
double
则相同循环的限制为479。gcc和clang也是如此

更新1


原来GCC7(快照)和clang(主干)的行为非常不同。就我所知,clang优化了所有小于960的限制的循环。另一方面,gcc对精确值很敏感,没有上限。例如,当限制为200(以及许多其他值)时,它不会优化循环,但当限制为202和20002(以及许多其他值)时,它会优化循环。

阅读Sulthan的评论后,我猜:

  • 如果循环计数器为常量(且不太高),编译器将完全展开循环

  • 展开后,编译器会看到可以将求和操作分组为一个

  • 如果循环由于某种原因没有展开(此处:它将生成太多带有
    1000
    的语句),则无法对操作进行分组

    编译器可以看到,1000条语句的展开相当于一次加法,但上面描述的步骤1和2是两个单独的优化,因此它不能承担展开的“风险”,因为不知道操作是否可以分组(例如:函数调用不能分组)

    注意:这是一个极端情况:谁使用循环再次添加相同的内容?在这种情况下,不要依赖编译器可能的展开/优化;直接在一条指令中写入正确的操作。

    TL;博士 默认情况下,当前快照GCC 7的行为不一致,而早期版本的默认限制为16。可以从命令行重写它

    该限制的基本原理是防止过于激进的循环展开,这可能是一个错误

    GCC版本非常好的问题

    在简化代码时,编译器尝试内联的迭代次数或操作次数似乎达到了限制。正如Grzegorz Szpetkowski所记录的,有一些特定于编译器的方法可以使用pragmas或命令行选项来调整这些限制


    您还可以使用来比较不同的编译器和选项如何影响生成的代码:
    gcc 6.2
    icc 17
    仍然内联960的代码,而
    clang 3.9
    没有(使用默认的Godbolt配置,它实际上在73处停止内联).

    Sulthan的意思可能是:1)编译器展开循环,2)一旦展开,就可以将求和操作分组为一个。如果循环未展开,则无法对操作进行分组。循环数为奇数会使展开更加复杂,最后几次迭代必须专门进行。这可能足以让优化器进入无法识别快捷方式的模式。很可能,它首先必须为特殊情况添加代码,然后再删除它。在耳朵之间使用优化器总是最好的:)@HansPassant它也针对任何小于959的数字进行了优化。这不是通常通过归纳变量消除来完成,而不是展开一个疯狂的数量吗?按959的倍数展开是疯狂的。@eleanora我用那个编译器资源管理器玩过,下面的说法似乎成立(仅讨论gcc快照):如果循环计数是4的倍数,并且至少是72,那么循环就不会展开(或者说,按4的倍数展开);否则,整个循环将替换为一个常量-即使循环计数为20000000001。我的怀疑是:过早的优化(比如说,过早的“嘿,4的倍数,这有利于展开”这一块进一步的优化,而不是更彻底的“这个循环到底是怎么回事?”),那么你能专注于
    不太高的部分吗?我的意思是为什么在
    100
    的情况下没有风险?我在上面的评论中猜到了一些东西。这可能是原因吗?我认为编译器没有意识到它可能触发的浮点错误。我想这只是指令大小的限制。您有
    max unrolled insns
    max unrolled times
    啊,这是我的想法或猜测……希望得到更清晰的推理。有趣的是,如果您将
    float
    更改为
    int
    ,gcc编译器能够在不考虑迭代次数的情况下降低循环强度,由于其感应变量优化(
    -fivopts
    )。但这些似乎不适用于
    float
    s.@CortAmmon,对吧,我记得读到一些人感到惊讶和不安的是,GCC使用MPFR精确计算非常大的数字,给出的结果与等效的浮点运算截然不同,后者会累积错误和精度损失。这说明很多人计算浮点的方法是错误的。我对这个问题进行了编辑,以澄清我使用的gcc和clang的版本。看见我用的是Ofast,谢谢你的回答。正如其他人所指出的,gcc似乎对确切的限制大小很敏感。例如,它无法消除912的循环。在这种情况下,fdump tree cunroll细节说明了什么?事实上,即使是200也有这个问题。这都是godbolt提供的gcc 7的快照。这根本不适用于叮当声。你解释了剥落的机理,但没有解释960的相关性,或者为什么它有一个极限all@M.M:GCC 6.3.0和最新的snaphost之间的剥离行为完全不同。对于前者,我强烈怀疑,硬编码限制是由
    PARAM_MAX_completed_PEEL_TIMES
    PARAM,t执行的
    float f(float x[]) {
      float p = 1.0;
      for (int i = 0; i < 960; i++)
        p += 1;
      return p;
    }
    
    .LCPI0_0:
            .long   1065353216              # float 1
    .LCPI0_1:
            .long   1086324736              # float 6
    f:                                      # @f
            vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
            vxorps  ymm1, ymm1, ymm1
            mov     eax, 960
            vbroadcastss    ymm2, dword ptr [rip + .LCPI0_1]
            vxorps  ymm3, ymm3, ymm3
            vxorps  ymm4, ymm4, ymm4
    .LBB0_1:                                # =>This Inner Loop Header: Depth=1
            vaddps  ymm0, ymm0, ymm2
            vaddps  ymm1, ymm1, ymm2
            vaddps  ymm3, ymm3, ymm2
            vaddps  ymm4, ymm4, ymm2
            add     eax, -192
            jne     .LBB0_1
            vaddps  ymm0, ymm1, ymm0
            vaddps  ymm0, ymm3, ymm0
            vaddps  ymm0, ymm4, ymm0
            vextractf128    xmm1, ymm0, 1
            vaddps  ymm0, ymm0, ymm1
            vpermilpd       xmm1, xmm0, 1   # xmm1 = xmm0[1,0]
            vaddps  ymm0, ymm0, ymm1
            vhaddps ymm0, ymm0, ymm0
            vzeroupper
            ret
    
    $ head test.c.151t.cunroll 
    
    ;; Function f (f, funcdef_no=0, decl_uid=1919, cgraph_uid=0, symbol_order=0)
    
    Not peeling: upper bound is known so can unroll completely
    
    if (maxiter >= 0 && maxiter <= npeel)
        {
          if (dump_file)
            fprintf (dump_file, "Not peeling: upper bound is known so can "
             "unroll completely\n");
          return false;
        }
    
    Loop 1 iterates 959 times.
    Loop 1 iterates at most 959 times.
    Not unrolling loop 1 (--param max-completely-peeled-times limit reached).
    Not peeling: upper bound is known so can unroll completely
    
    max-completely-peeled-insns
    
    max-completely-peel-times
    
    -march=core-avx2 -Ofast --param max-completely-peeled-insns=1000 --param max-completely-peel-times=1000
    
    f:
            vmovss  xmm0, DWORD PTR .LC0[rip]
            ret
    .LC0:
            .long   1148207104
    
    #pragma unroll
    for (int i = 0; i < 960; i++)
        p++;
    
    .LCPI0_0:
            .long   1148207104              # float 961
    f:                                      # @f
            vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
            ret