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

问题

我们有一个用于模拟任务的中型程序,我们需要优化。 我们已经尽我们最大的优化来源,以尽可能限制我们的编程技能,包括与Gprof和Valgrind分析。

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

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

问题

使用g ++的最新版本的可能的优化选项是什么,超越-O3 / -Ofast?

我们也对昂贵的小规模优化感兴趣,从长远来看这将是支出。

我们现在使用什么

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

  • -Ofast :最高“标准”优化级别。 包括-ffast-math计算在我们的计算中没有造成任何问题,所以我们决定去做,尽pipe不符合标准。
  • -march=native :启用所有CPU特定指令。
  • -flto允许在不同的编译单元中优化链接时间。

大多数答案都提出了另外的解决scheme,比如不同的编译器或者外部库,这很可能会带来大量的重写或集成工作。 我将尝试坚持问题的内容,并重点关注单独使用GCC可以做些什么,通过激活编译器标志或根据OP的要求对代码进行微小的更改。 这不是一个“你一定要这样做”的答案,而是更多的GCC调整集合,对我来说效果很好,你可以尝试一下,如果它们与你的具体情况相关。


有关原始问题的警告

在深入细节之前,关于这个问题的一些警告,通常是对于那些会来的人来说,阅读这个问题并且说“OP在O3之外优化,我应该使用与他相同的标志!”。

  • -march=native使得能够使用特定于给定CPU架构指令 ,并且不一定在不同的架构上可用。 如果在具有不同CPU的系统上运行,程序可能根本无法运行,或者运行速度明显较慢(因为这也会使mtune=native ),所以如果您决定使用它,请注意这一点。 更多信息在这里 。
  • -Ofast ,正如你所说的那样,使一些不符合标准的优化,所以它应该谨慎使用。 更多信息在这里 。

其他海湾合作委员会标志尝试

这里列出了不同标志的细节。

  • -Ofast -ffast-math ,反过来使-fno-math-errno-funsafe-math-optimizations-ffinite-math-only-fno-rounding-math-fno-signaling-nans -fcx-limited-range-fcx-limited-range 。 您可以通过select性地添加一些额外的标志 (如-fassociative-math-freciprocal-math-fno-signed-zeros-fno-trapping-math进一步优化浮点计算 。 这些不包括在-Ofast并且可以在计算上增加一些额外的性能提升,但是您必须检查它们是否确实有利于您,并且不会破坏任何计算。
  • GCC还具有一些其他优化标志 ,这些标志没有被任何“-O”选项启用。 它们被列为“可能产生破碎的代码的实验性选项”,所以应该谨慎使用它们,并通过testing正确性和基准来检查它们的影响。 不过,我经常使用-frename-registers ,这个选项对我来说从来没有产生不想要的结果,并且往往会导致性能的显着提高(即可以在基准testing时测量)。 这是非常依赖于你的处理器的标志的types。 -funroll-loops有时也会给出好的结果,但是它取决于你的实际代码。

PGO

GCC具有configuration文件引导优化function。 关于它的GCC文档并不多,但是让它运行起来相当简单。

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

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

GCC的并行模式

GCC具有一个并行模式 ,它在GCC 4.2编译器出现的时候首次发布。

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

关于这个并行模式的所有信息可以在这里find。

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

额外的技巧

不像以前的几个小节,这个部分确实需要对代码进行一些小的修改 。 它们也是GCC特有的(其中一些也是在Clang上工作的),所以编译时macros应该被用来保持代码在其他编译器上的可移植性。 本节包含一些更高级的技术,如果您不了解组件级别的情况,则不应使用该技术。 另外请注意,处理器和编译器现在非常聪明,所以从这里描述的函数中获得任何明显的好处可能会非常棘手。

  • GCC内置, 这里列出。 像__builtin_expect这样的构造可以通过提供分支预测信息来帮助编译器做更好的优化。 其他结构,如__builtin_prefetch ,在访问之前将数据带入caching,并有助于减lesscaching未命中
  • function属性, 这里列出。 尤其要注意cold属性, 前者会向编译器表明该函数是程序的一个热点 ,并更积极地优化函数,并将其放在文本段的特殊小节中,以获得更好的局部性; 后者将优化大小的function,并将其放在文本部分的另一个特殊的小节中。

我希望这个答案对于一些开发者来说是有用的,我会很乐意考虑任何编辑或者build议。

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

为什么不投资英特尔编译器和高性能库的副本? 在优化方面,它的performance可能会大幅度超过GCC,一般从10%到30%甚至更高,对于数量庞大的计划来说更是如此。 英特尔还为高性能数字运算(并行)应用程序提供了许多扩展和库,如果这是您可以负担得起的代码集成。 如果最终节省了几个月的运行时间,它可能会带来很大的收益。

我们已经将我们的最佳源码优化到了编程技能的极限

根据我的经验,与典型的优化(精简代码的结构)相比,通常在分析器的帮助下做的微观和纳米优化往往具有较差的投资回报(最重要的是,并且经常被忽视,存储器访问优化(例如,引用的地点,按顺序的遍历,最小化间接寻址,caching缺失等)。 后者通常涉及devise内存结构以更好地反映内存使用(穿越)的方式。 有时它可以像切换容器types一样简单,并从中获得巨大的性能提升。 通常情况下,通过configuration文件,您会迷失在逐条指令优化的细节中,并且内存布局问题不会显示出来,而且在忘记查看大图时通常会漏掉内存布局问题。 这是一个更好的方式来投资你的时间,并且回报可能是巨大的(例如,许多O(logN)algorithm最终执行几乎与O(N)一样慢因为内存布局不佳(例如,使用链表或链接树是一个典型的巨大的性能问题相比,一个连续的存储策略的罪魁祸首))。

如果您能负担得起,请尝试VTune 。 它提供了更多的信息比简单的抽样(由gprof提供,据我所知)。 您可以试试Code Analyst 。 后者是一个体面的,免费的软件,但它可能无法正确(或完全)与英特尔CPU工作。

配备这样的工具,它可以让你检查各种措施,如caching利用率(和基本的内存布局),如果使用它的全面扩展 – 提供了一个巨大的效率提升。

当你确定你的algorithm和结构是最优的,那么你应该使用i5和i7上的多核。 换句话说,玩弄不同的并行编程algorithm/模式,看看你是否可以加快速度。

当你有真正的并行数据(你执行类似/相同操作的类似数组的结构)时,你应该给OpenCL和SIMD指令 (更容易设置)一试。

嗯,那么你可以试试最后一件事: ACOVEA项目:通过演化algorithm分析编译器优化 – 从描述中可以看出 ,它试图使用遗传algorithm为项目select最好的编译器选项(编译时间和检查时机,给algorithm的反馈:) – 但结果可能令人印象深刻! 🙂

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

  • 什么types的数字嘎吱嘎吱?
  • 你在用什么库?
  • 什么程度的平等?

你能记下你的代码中花费时间最长的部分吗? (通常是一个紧密的循环)

如果你是CPU绑定的答案将是不同的,如果你是IO绑定。

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

关于目前select的答案的一些说明(我没有足够的声望点来发表评论):

答案是:

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

当答案发布时,这也许是正确的,但是GCC文档说所有这些都是通过-funsafe-math-optimizations启用的,这是由-ffast-math启用的-ffast-math启用的。 这可以使用命令gcc -c -Q -Ofast --help=optimizer进行检查,该命令显示哪些优化是由-Ofast启用的,并确认所有这些都已启用。

答案也是这样说的:

其他优化标志,这些标志没有被任何“-O”选项启用… -frename-registers

再一次,上面的命令显示,至less在我的GCC 5.4.0中, -frename-registers默认是使用-Ofast来启用的。

我会build议看看这些繁重的操作types,并寻找一个优化的库。 有相当多的快速,汇编优化,SIMD向量化的图书馆在那里常见的问题(主要是math)。 重新创造轮子往往是诱人的,但如果现有的soltuion能够满足你的需求,通常是不值得的。因为你没有说明是什么样的模拟,我只能提供一些例子。

http://www.yeppp.info/

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

https://github.com/xianyi/OpenBLAS

与gcc英特尔转/执行-fno-gcse(在gfortran上运行良好)和-fno-guess-branch-prbability(默认在gfortran)