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


72

问题

我们有一个需要优化的中型程序来执行模拟任务。我们已经尽最大努力优化了源代码,以达到编程技能的极限,包括使用GprofValgrind进行性能分析。

最终完成后,我们可能希望在多个系统上运行该程序几个月。因此,我们非常有兴趣将优化推向极限。

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

问题

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

我们也对代价高昂的次要优化感兴趣,从长远来看,这将有回报。

我们现在使用的

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

  • -Ofast:最高“标准”优化级别。所包含的内容-ffast-math在我们的计算中不会造成任何问题,因此尽管不符合标准,我们还是决定使用它。
  • -march=native:启用所有特定于CPU的指令的使用。
  • -flto 以便跨不同的编译单元优化链接时间。

7
您是否尝试过配置文件驱动的优化-尽管这将取决于是否具有用于分析的“代表性”数据。除此之外,我认为它可以识别热点并查看处理器生成的代码,并查看您是否可以更好地组织数据/代码或提出其他算法。
Mats Petersson

7
请注意,一天后启动程序并在那一天的优化中获得1%的性能提升,直到100天的运行时间后,收支平衡。换句话说,提前几天运行程序可能会超出小的优化范围。
2013年

3
@sth:这是真的。但是,我希望找到一些可以在以后的项目中重用的提示/技巧,因此我不必花时间从优化中获胜...
Haatschii 2013年

2
@OliCharlesworth:您可能是正确的,所以我列举了一个明确的例子。但是,我希望可能会有一些标志/技巧,其产生的效果可能甚至比较小的提速还要多。
Haatschii 2013年

1
根据本博客,我没有检查自己,但-ffast-math并不总是使代码更快。
tr3w

Answers:


84

大多数答案都建议使用替代解决方案,例如不同的编译器或外部库,这很可能会带来大量的重写或集成工作。我将尝试坚持问题的实质,并按照OP的要求,通过激活编译器标志或对代码进行最少的更改来专注于单独使用GCC可以做什么。这不是一个“必须做的事情”的答案,而是更多的GCC调整的集合,这些调整对我来说效果很好,如果在您的特定情况下相关,您可以尝试一下。


有关原始问题的警告

在详细讨论之前,通常会给那些会问这个问题的人一些警告,阅读这些问题并说:“ OP正在优化O3之外,我应该使用与他相同的标志!”。

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

其他GCC标志可试用

此处列出了不同标志的详细信息。

  • -Ofast使-ffast-math,这反过来又使-fno-math-errno-funsafe-math-optimizations-ffinite-math-only-fno-rounding-math-fno-signaling-nans-fcx-limited-range。你可以去进一步浮点运算的优化选择地增加一些额外的标志,例如-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标志。

带有GCC的PGO可以产生惊人的结果,并确实可以显着提高性能(我最近看到的一个项目的速度提高了15-20%)。显然,这里的问题是要有一些足够具有代表性的数据您的应用程序执行的数据,这些数据并不总是可用或容易获得。

GCC的并行模式

GCC具有Parallel Mode,该模式在GCC 4.2编译器退出时首次发布。

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

有关此并行模式的所有信息都可以在此处找到。

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

额外的技巧

与前面的要点部分不同,此部分确实需要对代码进行一些小的更改。它们也是GCC特定的(其中一些也可以在Clang上工作),因此应使用编译时宏来使代码在其他编译器上可移植。本节包含一些更高级的技术,如果您对组装过程不了解,请不要使用本节。还要注意,处理器和编译器如今非常聪明,因此从此处描述的功能中获得任何明显的好处可能很棘手。

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

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


3
谢谢,这个答案几乎描述了我们最终要做的事情,特别是PGO被证明是非常有用的。另外,我也喜欢@zaufi建议的ACOVEA项目,尽管该项目无法解决。
哈茨伊(Haatschii),2016年

3
哇,不了解PGO选项!我的情况大约改善了30%。
fhucho

1
我很确定这是错误的,“-Ofast中未包含这些”。如果您查看-ffast-math的GCC文档(由-Ofast启用),它还会启用-funsafe-math优化,从而启用-fassociative-math。(除其他外)在文档中有一句话“任何-O选项都未打开此选项”,我认为这是文档错误,因为-Ofast确实将其打开。同样,PGO打开-funroll-loops,这将打开-frename-registers。
uLoop

@uLoop:GCC文档确实并不总是很清楚。我已经使用编译器的-Q标志检查了这些标志,并相应地调整了答案。
皮夫斯

1
@Pyves我也遇到了另一种与您赞美的方法:使用GCC和Perf进行反馈指导的优化:blog.wnohang.net/index.php/2015/04/29/… 但是,对此有疑问的是,本文并不是最新的,一些命令已被弃用,gcov_create在读取perf的perf.data文件时遇到问题。也许您可以调查并提供一些指导。
cryptoboy '20

18

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

为什么不投资购买英特尔编译器和高性能库的副本?在优化方面,它的性能可比GCC大得多,通常从10%到30%甚至更高,对于繁重的数字处理程序而言,甚至更高。英特尔还为高性能的数字运算(并行)应用程序提供了许多扩展和库,如果您有能力将其集成到代码中的话。如果最终节省了您几个月的运行时间,它可能会带来很大的回报。

我们已经尽了最大努力来优化源代码,以达到我们编程技能的极限。

根据我的经验,与宏优化(简化代码结构)相比,通常在事件探查器的帮助下进行的微优化和纳米优化的时间投资回报率往往较低。并且经常被忽略的是内存访问优化(例如,引用的局部性,有序遍历,最小化间接访问,浪费高速缓存丢失等)。后者通常涉及设计存储器结构,以更好地反映使用(遍历)存储器的方式。有时,它就像切换容器类型并从中获得巨大的性能提升一样简单。通常,使用探查器时,您会迷失于逐条指令优化的细节中,并且不会出现内存布局问题,并且在忘记看大图时通常会忽略它们。它'


我们尚未使用intel编译器的原因是它不支持我们正在使用的某些C ++ 11功能。如果这种变化足够快,我们也将尝试ICC。我大部分都同意你的第二部分。但是除了让更多的人看一下代码之外,我看不到如何进一步改进它。因此,我的问题是,是否还有更多可以使编译器做的事情。
2013年

2
@Haatschii是的,很抱歉,我无法直接回答您的问题(即,如何从GCC中获得最大收益),因为我认为您无法做到。我只是认为值得将这几点(使用ICC并进行内存优化)作为更好的途径来实际实现您的目标。
Mikael Persson

2
我对这种说法“通常从10%到30%甚至更高”持怀疑态度。至少,这些余量远远超出了我在自己的工作中测得的水平。我很乐意看到已发布的基准测试证明了这一点,前提是要使用等效的编译器标志并发布所使用的标志(如果只是为了查看是否错过了英特尔编译器的优化机会)。
apmccartney

7

呵呵,然后您可以尝试做的最后一件事:ACOVEA项目:通过进化算法分析编译器优化-从描述中可以明显看出,它尝试使用遗传算法为您的项目选择最佳的编译器选项(进行多次编译并检查是否时间,给算法一个反馈:)-但是结果可能会令人印象深刻!:)


6

如果负担得起,请尝试VTune。它提供了比简单采样更多的信息(据我所知gprof提供)。您可以尝试一下代码分析师。Latter是一款不错的免费软件,但它可能无法(或根本无法)在Intel CPU上正常工作。

配备了这样的工具,它使您可以检查各种指标,例如缓存利用率(基本上是内存布局),如果充分利用了这些指标,则可以极大地提高效率。

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

当您拥有真正的并行数据(在其上执行相似/相同操作的类似数组的结构)时,应尝试使用OpenCL和SIMD指令(更易于设置)。


4

关于当前选择答案的一些注意事项(我尚无足够的声誉积分才能发表此评论):

答案说:

-fassociative-math-freciprocal-math-fno-signed-zeros,和-fno-trapping-math。这些不包含在其中-Ofast,可以在计算上带来一些额外的性能提升

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

答案还说:

其他“ -O”选项未启用的优化标志... -frename-registers

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


1

没有更多细节,很难回答:

  • 什么类型的数字运算?
  • 您正在使用哪些库?
  • 什么程度的并行化?

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

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

同样,请提供更多详细信息。


1

我建议您看一下给繁重工作带来麻烦的操作类型,并寻找一个优化的库。对于常见问题(主要是数学),那里有很多快速的,经过装配优化的SIMD矢量化库。重塑轮子通常很诱人,但是如果现有的解决方案能够满足您的需求,通常就不值得付出努力。由于您尚未说明是哪种模拟,所以我只能提供一些示例。

http://www.yeppp.info/

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

https://github.com/xianyi/OpenBLAS


-3

使用/ cc的gcc intel轮换执行-fno-gcse(在gfortran上运行良好)和-fno-guess-branch-prbability(gfortran中的默认设置)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.