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

Haa*_*hii 57 c++ g++ compiler-optimization

问题

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

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

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

问题

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

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

我们现在用的是什么

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

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

Pyv*_*ves 58

大多数答案都提出了替代解决方案,例如不同的编译器或外部库,这很可能会带来大量的重写或集成工作.我将尽力坚持问题所针对的问题,并着眼于单独使用GCC可以做什么,通过激活编译器标志或对OP进行最小的代码更改.这不是"你必须这样做"的答案,而是更多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具有Profile-Guided Optimisations功能.关于它没有很多精确的GCC文档,但是让它运行起来非常简单.

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

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

GCC的并行模式

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

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

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

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

额外的技巧

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

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

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

  • 谢谢,这个答案几乎描述了我们最终做的事情,特别是PGO被证明是非常有用的.另外我也很喜欢@zaufi建议的ACOVEA项目,虽然它没有用于这个项目. (3认同)
  • 哇,不知道PGO选项!我的案子改善了大约30%. (3认同)

Mik*_*son 18

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

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

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

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

  • @Haatschii是的,对不起,我不能直接回答你的问题(即如何从GCC中挤出最多),因为我认为你不能.我只是认为将那些少数几点(使用ICC并进行内存优化)作为实现目标的更好途径是值得的. (2认同)
  • 我对这种说法持怀疑态度"通常从10%到30%甚至更多".至少,这些利润远远超出我在自己的工作中衡量的范围.我很乐意看到一个已发布的基准测试集合,证明了这一点,只要使用等效的编译器标志并且使用了标志,只要看看我是否错过了英特尔编译器的优化机会. (2认同)

Red*_*III 6

如果你负担得起,试试VTune.它比简单的采样提供了更多的信息(据我所知,由gprof提供).您可以尝试代码分析师.Latter是一款不错的免费软件,但它可能无法正常(或根本不能)与英特尔CPU配合使用.

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

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

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


zau*_*ufi 5

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