为什么当限制是959而不是960时,简单的循环会被优化? [英] Why is a simple loop optimized when the limit is 959 but not 960?

查看:37
本文介绍了为什么当限制是959而不是960时,简单的循环会被优化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下简单循环:

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 959; i++)
    p += 1;
  return p;
}

如果您使用-march=core-avx2 -Ofast使用GCC 7(快照)或Clang(主干)进行编译,则会得到与非常相似的结果。

.LCPI0_0:
        .long   1148190720              # float 960
f:                                      # @f
        vmovss  xmm0, dword ptr [rip + .LCPI0_0] # xmm0 = mem[0],zero,zero,zero
        ret

换句话说,它只将答案设置为960,而不循环。

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

float f(float x[]) {
  float p = 1.0;
  for (int i = 0; i < 960; i++)
    p += 1;
  return p;
}

所生成的程序集是否实际执行循环求和?例如clang给出:

.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

为什么会这样,为什么Cang和GCC的情况完全一样?


如果将float替换为double,则同一循环的限制为479。GCC和叮当也是这样。

更新%1

原来GCC 7(快照)和Cang(树干)的行为非常不同。根据我的判断,clang会针对所有小于960的限制优化循环。GCC则对精确值很敏感,没有上限。例如,当限制为200时(以及许多其他值)时,不会优化循环,但当限制为202时和20002(以及许多其他值)时,优化循环。

推荐答案

TL;DR

默认情况下,当前快照GCC 7的行为不一致,而之前的版本由于PARAM_MAX_COMPLETELY_PEEL_TIMES有默认限制,为16,可以通过命令行覆盖。

限制的基本原理是防止过于激进的循环展开,这可能是double-edged sword

GCC版本<;=6.3.0

GCC的相关优化选项为-fpeel-loops,随标志-Ofast间接启用(重点为我的):

剥离有足够信息但没有足够信息的循环 滚动大量(来自个人资料反馈或静电分析)。它还会打开 完全剥离毛圈(即完全去除具有小尺寸毛圈的毛圈 恒定迭代次数)。

使用-O3和/或-fprofile-use启用。

更多详情请添加-fdump-tree-cunroll

$ 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

邮件来自/gcc/tree-ssa-loop-ivcanon.c

if (maxiter >= 0 && maxiter <= npeel)
    {
      if (dump_file)
        fprintf (dump_file, "Not peeling: upper bound is known so can "
         "unroll completely
");
      return false;
    }

因此try_peel_loop函数返回false

使用-fdump-tree-cunroll-details

可以获得更详细的输出
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=nmax-completely-peel-times=n参数来调整限制:

max-completely-peeled-insns

完全剥离循环的最大INSN数。

max-completely-peel-times
适合完成的循环的最大迭代次数 正在剥离。

若要了解有关INSNS的更多信息,请参阅GCC Internals Manual

例如,如果您使用以下选项进行编译:

-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

点击

我不确定Clang实际做什么以及如何调整其限制,但正如我所观察到的,您可以通过用unroll pragma标记循环来强制它计算最终值,然后它会将其完全删除:

#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

这篇关于为什么当限制是959而不是960时,简单的循环会被优化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆