【问题标题】:Why are compiler outputs so different for this simple function?为什么这个简单函数的编译器输出如此不同?
【发布时间】:2018-02-11 19:04:28
【问题描述】:

我要求 C++ 编译器生成一个机器编码函数,该函数对 const std::vector 的元素求和。

#include <vector>

int sum(const std::vector<int>& v)
{
    int s = 0;
    for(const auto e:v) s += e;
    return s;
}

我用-O2 询问编译器x86_64 GCC 7.2。它只是说:

sum(std::vector<int, std::allocator<int> > const&):
  mov rdx, QWORD PTR [rdi]
  mov rcx, QWORD PTR [rdi+8]
  xor eax, eax
  cmp rdx, rcx
  je .L4
.L3:
  add eax, DWORD PTR [rdx]
  add rdx, 4
  cmp rdx, rcx
  jne .L3
  rep ret
.L4:
  rep ret

我对带有-O1-O2-O3 的编译器Clang 4.0.0 提出同样的要求。好吧!那是一篇完整的论文。它只是在-O1-O2-O3 的文章中操纵了一些形容词。

类似的情况是 x86_64 ICC 17 和 -O2 和 x86_64 CL 19。

然后我返回编译器 x86_64 GCC 7.2 并使用 -O3。 x86_64 GCC 7.2 现在给出了一个冗长的输出。

【问题讨论】:

  • 最好发布一个完整的示例,其中包含可以编译的代码或带有代码的 goldbolt 链接。现在,答案可能对您没有帮助。此外,虽然这个问题读起来很有趣,但使用尽可能具体的术语而不是“论文”等将有助于人们理解您的问题。
  • 不看汇编很难解释它,但我猜你可能会看到“循环展开”,这是一种通过使用更多指令使代码运行得更快的技术。
  • 这个函数很容易向量化,所以难怪编译器会向量化它。
  • @user62039 Godbolt 有一个“分享”按钮,您可以在其中创建一个链接,以便其他人可以看到代码、程序集以及您正在查看的所有其他内容。
  • 组装长度是一个值得商榷的指标,但当涉及到循环时,它就完全没用了。是的,矢量化和展开版本更长,但在相同输入上需要的迭代次数要少得多。

标签: c++ gcc visual-c++ c++14 clang++


【解决方案1】:

我认为你的问题是:

为什么这么简单的事情会产生这么多代码?

答案很长,但大致如下:

当我们操作内存时,现代内存架构实现了更高的吞吐量...

  • 以块为单位
  • 并行
  • 关于对给定硬件有效的内存地址边界。

因此编译器试图通过尽可能矢量化/并行化操作来优化长向量的情况,同时考虑到内存架构的特性。

但并非所有向量都是可以有效地视为一个块的项目数的倍数。

因此,向量的开头和结尾通过特定的操作分开处理,而(长)向量的大部分由中间的巧妙向量化代码处理。

【讨论】:

  • 谢谢!在阅读了您的答案之后,尤其是您所说的“开始和结束......分开处理”并更仔细地回顾 GCC -O3 输出后,我注意到大部分发出的代码实际上只为边界运行一次元素。
猜你喜欢
  • 1970-01-01
  • 2012-02-27
  • 1970-01-01
  • 2011-07-08
  • 1970-01-01
  • 1970-01-01
  • 2016-07-30
  • 2013-02-23
  • 1970-01-01
相关资源
最近更新 更多