超出-O3 / -Ofast的G ++优化

时间:2013-01-24 01:27:42

标签: c++ g++ compiler-optimization

问题

我们有一个用于模拟任务的中型程序,我们需要进行优化。我们已经尽最大努力优化我们的编程技能,包括使用GprofValgrind进行性能分析。

当我们最终完成时,我们希望在几个系统上运行该程序可能已有几个月了。因此,我们真的很有兴趣将优化推向极限。

所有系统都将在相对较新的硬件(Intel i5或i7)上运行Debian / Linux。

问题

使用最新版本的g ++有哪些可能的优化选项,超出-O3 / -Ofast?

我们也对代价高昂的小优化感兴趣,从长远来看,这将是非常重要的。

我们现在使用的是什么

现在我们使用以下g ++优化选项:

  • -Ofast:最高“标准”优化级别。所包含的-ffast-math在我们的计算中没有引起任何问题,所以我们决定采用它,尽管非标准合规。
  • -march=native:启用所有CPU特定指令。
  • -flto允许跨不同的编译单元进行链接时优化。

8 个答案:

答案 0 :(得分:51)

大多数答案都提出了替代解决方案,例如不同的编译器或外部库,这很可能会带来大量的重写或集成工作。我将尽力坚持问题所针对的问题,并着眼于单独使用GCC可以做什么,通过激活编译器标志或对OP进行最小的代码更改。这不是"你必须这样做"回答,但更多的GCC调整集合对我来说效果很好,如果它们与您的特定背景相关,您可以尝试一下。

有关原始问题的警告

在进入细节之前,有一些关于这个问题的警告,通常对于那些将会出现的人来说,阅读问题然后说'OP优化超出O3,我应该使用与他相同的标志! "

  • -march=native允许使用特定于给定CPU架构的指令,并且不一定在其他架构上可用。如果在具有不同CPU的系统上运行,或者速度明显较慢(因为这也启用mtune=native),程序可能根本不起作用,因此如果您决定使用它,请注意这一点。更多信息here
  • 正如您所说,
  • -Ofast启用了一些非标准兼容的优化,因此也应谨慎使用。更多信息here

试用的其他GCC标志

列出了不同标志的详细信息here

  • -Ofast启用-ffast-math-fno-math-errno依次启用-funsafe-math-optimizations-ffinite-math-only-fno-rounding-math-fno-signaling-nans-fcx-limited-range和{ {1}}。您可以选择添加一些额外标记,例如-fno-signed-zeros-fno-trapping-math等来进一步了解浮点计算优化。这些不包括在-Ofast中,并且可以在计算上提供一些额外的性能提升,但您必须检查它们是否真正使您受益并且不会破坏任何计算。
  • GCC还提供了大量其他优化标记,这些标志不会被任何" -O"选项。它们被列为"实验性选项,可能会产生损坏的代码",因此,应谨慎使用它们,并通过测试正确性和基准测试来检查它们的效果。尽管如此,我经常使用-frename-registers,这个选项从来没有给我带来不必要的结果,并且往往会带来明显的性能提升(即可以在基准测试时测量)。这是一种非常依赖于您的处理器的标志类型。 -funroll-loops有时也会产生良好的效果(也会隐含-frename-registers),但这取决于您的实际代码。

<强> PGO

GCC具有配置文件引导优化功能。关于它没有很多精确的GCC文档,但是让它运行起来非常简单。

  • 首先使用-fprofile-generate
  • 编译您的程序
  • 让程序运行(执行时间将显着减慢,因为代码也会将配置文件信息生成为.gcda文件)。
  • 使用-fprofile-use重新编译程序。如果您的应用程序是多线程的,还要添加-fprofile-correction标志。

PGO与GCC可以给出惊人的结果并且真正显着提升性能(我已经看到我最近在其中一个项目上加速了15-20%)。显然,这里的问题是有一些数据具有足够的代表性您的应用程序的执行,这并不总是可用或很容易获得。

GCC的并行模式

GCC采用并行模式,这是在GCC 4.2编译器出来的时候首次发布的。

基本上,它为您提供了C ++标准库中许多算法的并行实现。要在全局启用它们,您只需将-fopenmp-D_GLIBCXX_PARALLEL标志添加到编译器。您还可以在需要时有选择地启用每个算法,但这需要进行一些次要的代码更改。

可以找到有关此并行模式的所有信息here

如果您经常在大型数据结构上使用这些算法,并且有许多可用的硬件线程上下文,则这些并行实现可以提供巨大的性能提升。到目前为止,我只使用了sort的并行实现,但是为了给出一个粗略的想法,我设法在我的一个应用程序中将排序时间从14秒缩短到4秒(测试环境:1亿的向量)具有自定义比较器功能和8核机器的对象。)

额外技巧

与前面的各个部分不同,此部分需要对代码进行一些小的更改。它们也是GCC特有的(其中一些也适用于Clang),因此应该使用编译时宏来保持代码在其他编译器上的可移植性。本节包含一些更高级的技巧,如果您对某些内容没有一定程度的了解,则不应使用此技术。另请注意,处理器和编译器现在非常智能,因此从这里描述的功能中获得任何明显的好处可能会很棘手。

  • GCC builtins,列出here。像__builtin_expect这样的构造可以通过为编译器提供分支预测信息来帮助编译器进行更好的优化。其他构造(例如__builtin_prefetch)会在访问数据之前将数据放入缓存中,并有助于减少缓存未命中
  • 列出here的函数属性。特别是,您应该查看hotcold属性;前者将向编译器指示该函数是程序的热点并更积极地优化函数并将其放在文本部分的特殊子部分中,以获得更好的局部性;后者将优化函数的大小,并将其放在文本部分的另一个特殊小节中。

我希望这个答案对某些开发人员有用,我很乐意考虑任何编辑或建议。

答案 1 :(得分:18)

  

相对较新的硬件(Intel i5或i7)

为什么不投资Intel compiler和高性能库的副本?它可以在优化方面优于GCC,通常从10%到30%甚至更高,对于繁重的数字运算程序更是如此。英特尔还为高性能数字运算(并行)应用程序提供了许多扩展和库,如果这是你可以负担得起的代码。如果它最终能节省你几个月的运行时间,它可能会带来巨大回报。

  

我们已经尽最大努力优化我们的编程技能极限

根据我的经验,与宏优化(简化代码结构)相比,通常在分析器的帮助下进行的微观和纳米优化往往具有较差的时间投资回报率。最重要且经常被忽略的是存储器访问优化(例如,参考的局部性,有序遍历,最小化间接,消除高速缓存未命中等)。后者通常涉及设计存储器结构以更好地反映存储器的使用方式(遍历)。有时它可以像切换容器类型一样简单,并从中获得巨大的性能提升。通常,对于分析器,您会逐渐失去指令优化的细节,并且内存布局问题不会出现,并且在忘记查看大图时通常会错过。这是一种更好的投资时间的方式,并且收益可能很大(例如,许多O(logN)算法最终执行的速度几乎与O(N)一样慢,仅仅因为内存布局不佳(例如,使用链接列表)或链接树是一个典型的巨大性能问题的罪魁祸首,与连续存储策略相比))。

答案 2 :(得分:6)

如果您负担得起,请尝试VTune。它比简单的采样提供了更多的信息(据我所知,由gprof提供)。您可以尝试Code Analyst。 Latter是一款不错的免费软件,但它可能无法正常(或者根本不能)使用Intel CPU。

配备这样的工具,它允许您检查各种措施,如缓存利用率(基本上是内存布局),如果完全使用它,可以大大提高效率。

如果您确定算法和结构是最佳的,那么您肯定应该在i5和i7上使用多个核心。换句话说,使用不同的并行编程算法/模式,看看你是否可以加快速度。

如果您拥有真正的并行数据(类似于数组的结构,您可以在其上执行类似/相同的操作),那么您应该尝试使用OpenCL和SIMD instructions(更容易设置)。

答案 3 :(得分:5)

呵呵,那么你可以尝试最后的事情:ACOVEA项目:通过进化算法分析编译器优化 - 从描述中可以明显看出,它尝试遗传算法为您的项目选择最佳编译器选项(做编译maaany时间并检查时间,给算法反馈:) - 但结果可能令人印象深刻! :)

答案 4 :(得分:2)

关于当前选择的答案的一些注释(我没有足够的声誉点发布此评论):

答案是:

  

-fassociative-math-freciprocal-math-fno-signed-zeros-fno-trapping-math。这些不包括在-Ofast中,并且可以在计算上提供一些额外的性能提升

在发布答案时可能会出现这种情况,但GCC documentation表示所有这些都由-funsafe-math-optimizations启用,由-ffast-math启用,由{-Ofast启用1}}。可以使用gcc -c -Q -Ofast --help=optimizer命令检查这一点,该命令显示-Ofast启用了哪些优化,并确认所有这些优化都已启用。

答案还说:

  

其他优化标记未被任何“-O”选项启用... -frename-registers

同样,上述命令显示,至少在我的GCC 5.4.0中,-frename-registers默认启用-Ofast

答案 5 :(得分:1)

如果没有进一步的细节,很难回答:

  • 什么类型的数字嘎吱嘎吱?
  • 你在使用哪些图书馆?
  • 什么程度的paralelization?

你能写下代码中耗时最长的部分吗? (通常是紧密循环)

如果您受CPU限制,答案将与您的IO绑定不同。

再次,请提供进一步的细节。

答案 6 :(得分:0)

我建议您查看繁重工作的操作类型,并寻找优化的库。有很多快速,装配优化的SIMD矢量化库可以解决常见问题(主要是数学)。重新发明轮子往往很诱人,但如果现有的解决方案可以满足您的需求,通常不值得努力。由于您没有说明什么样的模拟,我只能提供一些例子。

http://www.yeppp.info/

http://eigen.tuxfamily.org/index.php?title=Main_Page

https://github.com/xianyi/OpenBLAS

答案 7 :(得分:-2)

使用gcc intel转向/实现-fno-gcse(在gfortran上运行良好)和-fno-guess-branch-prbability (默认在gfortran)