87

问题

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

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

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

问题

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

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

我们现在使用的

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

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

8 回答 8

101

大多数答案都建议了替代解决方案,例如不同的编译器或外部库,这很可能会带来大量的重写或集成工作。我将尝试坚持问题的要求,并关注单独使用 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 优化功能。没有很多关于它的精确 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数据被访问之前将数据带入缓存,并有助于减少缓存未命中
  • 功能属性,在此处列出。特别是,您应该查看hotandcold属性;前者将向编译器指示该函数是程序的热点,并更积极地优化该函数并将其放置在文本部分的特殊小节中,以获得更好的局部性;后者将优化函数的大小并将其放置在文本部分的另一个特殊小节中。

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

于 2016-07-21T18:33:58.950 回答
18

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

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

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

根据我的经验,与宏观优化(简化代码结构)相比,您通常在分析器的帮助下进行的那种微观和纳米优化往往在时间投资上的回报很差,最重要的是并且经常被忽视的内存访问优化(例如,引用的局部性、按顺序遍历、最小化间接性、使用缓存未命中等)。后者通常涉及设计内存结构以更好地反映内存的使用方式(遍历)。有时它可以像切换容器类型并从中获得巨大的性能提升一样简单。通常,使用分析器,您会迷失在逐条指令优化的细节中,并且内存布局问题不会出现,并且通常会在忘记查看大图时被忽略。它'

于 2013-01-24T02:16:40.630 回答
8

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

于 2013-01-24T01:43:55.530 回答
7

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

配备此类工具后,您可以检查各种措施,例如缓存利用率(以及基本上是内存布局),如果充分利用这些措施,可以极大地提高效率。

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

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

于 2013-01-24T10:17:28.363 回答
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.

于 2017-07-29T10:48:25.347 回答
1

没有更多细节很难回答:

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

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

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

再次,请提供更多详细信息。

于 2013-01-24T03:25:06.087 回答
1

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

http://www.yeppp.info/

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

https://github.com/xianyi/OpenBLAS

于 2015-12-16T22:01:45.000 回答
-3

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

于 2015-07-11T10:07:42.330 回答