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