【问题标题】:Is there a good reason why GCC would generate jump to jump just over one cheap instruction?GCC 是否有充分的理由生成跳转以跳过一条廉价指令?
【发布时间】:2018-08-31 01:34:02
【问题描述】:

我正在对循环代码中的一些计数进行基准测试。 g++ 与 -O2 代码一起使用,我注意到当某些条件在 50% 的情况下为真时,它会出现一些性能问题。我认为这可能意味着代码会进行不必要的跳转(因为 clang 会产生更快的代码,所以它不是一些基本限制)。

我在这个 asm 输出中发现有趣的是代码跳过了一个简单的添加。

=> 0x42b46b <benchmark_many_ints()+1659>:       movslq (%rdx),%rax
   0x42b46e <benchmark_many_ints()+1662>:       mov    %rax,%rcx
   0x42b471 <benchmark_many_ints()+1665>:       imul   %r9,%rax
   0x42b475 <benchmark_many_ints()+1669>:       shr    $0xe,%rax
   0x42b479 <benchmark_many_ints()+1673>:       and    $0x1ff,%eax
   0x42b47e <benchmark_many_ints()+1678>:       cmp    (%r10,%rax,4),%ecx
   0x42b482 <benchmark_many_ints()+1682>:       jne    0x42b488 <benchmark_many_ints()+1688>
   0x42b484 <benchmark_many_ints()+1684>:       add    $0x1,%rbx
   0x42b488 <benchmark_many_ints()+1688>:       add    $0x4,%rdx
   0x42b48c <benchmark_many_ints()+1692>:       cmp    %rdx,%r8
   0x42b48f <benchmark_many_ints()+1695>:       jne    0x42b46b <benchmark_many_ints()+1659>

请注意,我的问题不是如何修复我的代码,我只是问是否有理由说明 O2 的优秀编译器会生成 jne 指令以跳过 1 条廉价指令。 我问是因为从what I understand 可以“简单地”获得比较结果并使用它来将计数器(在我的示例中为 rbx)增加 0 或 1。

编辑:来源: https://godbolt.org/z/v0Iiv4

【问题讨论】:

  • 发布生成此程序集的 C/C++ 代码可能是个好主意?

标签: gcc assembly compiler-optimization


【解决方案1】:

来源的相关部分(来自评论中的 Godbolt 链接,您应该真正编辑到您的问题中)是:

const auto cnt = std::count_if(lookups.begin(), lookups.end(),[](const auto& val){
    return buckets[hash_val(val)%16] == val;});

我没有检查 libstdc++ 标头以查看 count_if 是否使用 if() { count++; } 实现,或者它是否使用三元来鼓励无分支代码。应该是有条件的。 (编译器可以选择其中任何一个,但三元组更有可能编译为无分支的cmovccsetcc。)


gcc 似乎高估了此代码通过通用调优实现无分支的成本-mtune=skylake(由 -march=skylake 暗示)为我们提供了无分支代码,无论 -O2-O3,或 -fno-tree-vectorize-ftree-vectorize。 (在the Godbolt compiler explorer 上,我还将计数放在一个单独的函数中,该函数对vector&lt;int&gt;&amp; 进行计数,因此我们不必在main 中处理时间和cout 代码生成。)

  • 分支代码:gcc8.2-O2-O3,和O2/3 -march=haswellbroadwell
  • 无分支代码:gcc8.2 -O2/3 -march=skylake.

这很奇怪。它发出的无分支代码在 Broadwell 和 Skylake 上具有相同的成本。我想知道 Skylake 与 Haswell 是否因为更便宜的 cmov 而偏爱无分支。 GCC 的内部成本模型在中端优化时并不总是根据 x86 指令(在 GIMPLE 中,一种与架构无关的表示)。它还不知道什么 x86 指令实际上将用于无分支序列。因此,可能涉及条件选择操作,并且 gcc 在 Haswell 上将其建模为更昂贵,其中cmov 是 2 微秒?但我测试了-march=broadwell,仍然得到了分支代码。希望我们可以排除假设 gcc 的成本模型知道 Broadwell(不是 Skylake)是第一个具有单微指令 cmovadcsbb(3 输入整数运算)的英特尔 P6/SnB 系列 uarch )。

我不知道 gcc 的 Skylake 调优选项还有什么其他优点,这使得它有利于这个循环的无分支代码。 Gather 在 Skylake 上是高效的,但 gcc 是自动矢量化的(使用 vpgatherqd xmm),即使使用 -march=haswell,它看起来并不像一个胜利,因为收集很昂贵,并且需要 32x64 => 64 位 SIMD 乘法使用每个输入向量 2x vpmuludq。 SKL 也许值得,但我怀疑 HSW。也可能错过了优化,不打包回双字元素以收集两倍的元素,而vpgatherdd 的吞吐量几乎相同。

我确实排除了优化程度较低的函数,因为它被称为main(并标记为cold)。通常建议不要将您的微基准放在 main 中:编译器至少用于以不同方式优化 main(例如,针对代码大小而不仅仅是速度)。


即使仅使用 -O2,Clang 也确实使其无分支。


当编译器必须在分支和分支之间做出决定时,他们有启发式方法来猜测哪个会更好。如果他们认为它是高度可预测的(例如,可能大部分都没有被拍摄),那就倾向于分支。

在这种情况下,启发式算法可能已经确定,在 int 的所有 2^32 个可能值中,很少能准确找到您正在寻找的值。 == 可能欺骗了 gcc,认为它是可以预测的。

有时分支会更好,这取决于循环,因为它可以破坏数据依赖性。请参阅gcc optimization flag -O3 makes code slower than -O2 了解它 非常可预测的情况,而-O3 无分支代码生成速度较慢。

-O3 至少曾经更积极地将条件句转换为无分支序列,如 cmplea 1(%rbx), %rcx; cmove %rcx, %rbx,或者在这种情况下更可能是xor-zero / cmp/ sete / add。 (实际上 gcc -march=skylake 使用 sete / movzx,严格来说更糟。)

如果没有任何运行时分析/检测数据,这些猜测很容易出错。 这样的东西是配置文件引导优化的亮点。用-fprofile-generate 编译,运行它,然后用-fprofile-use 编译,你可能会得到无分支代码。


顺便说一句,现在通常推荐-O3Is optimisation level -O3 dangerous in g++?。它确实默认启用-funroll-loops,因此它只会在自动矢量化时使代码膨胀(尤其是在tiny SIMD 周围有非常大的完全展开的标量序言/尾声在循环开销上遇到瓶颈的循环。/facepalm。)

【讨论】:

  • @NoSenseEtAl:看起来像是一个错过的优化,但有趣的是-march=skylake 给了我们无分支代码。
  • @NoSenseEtAl:您是否尝试过将配置文件引导优化与其他调整一起使用?例如-march=native 这解决了另一种情况下的问题,在链接问题中-O3-O2 慢。
  • @NoSenseEtAl:是的,我也试过了。在我的机器上使用 PGO 会更快(gcc7.3 -O3 使用默认的 tune=generic),因为它更好地布置了分支。不过,该分支非常可预测,至少在 Skylake 上是这样。 perf stat 表示整个程序对于分支版本的分支错误预测率为 0.16%。 PGO 保留分支并不奇怪,因为它的预测非常好。不过,-march=skylake(无分支)稍微快一些,所以我猜这是错误的选择。 (~722M 时钟周期无分支,~790M PGO,814M 普通 -O3。)
  • 关于分支:我在我的godbolt链接中犯了错误:发现/没有定期找到替代品,我应该在创建查找时使用 rand()...
  • @NoSenseEtAl:这很奇怪,即使使用-O3 -mtune=skylake,gcc 7.3 也不会生成无分支代码,即使使用 PGO 也不会。我看到 3.4% 的分支错误预测率。 (我意识到我之前用 -O3 -march=skylake 进行的测试是自动矢量化的;这可能就是它稍微快一点的原因!事实上,随机版本也更快,因为 SKL 具有快速聚集。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-17
  • 2017-05-24
  • 1970-01-01
  • 1970-01-01
  • 2019-09-12
相关资源
最近更新 更多