视频

了解更多
Redis 和 Intel 合作,以了解应用更积极的优化选项是否能提高 Redis 的整体基准性能。我们的结论是:是的!通过改变编译器的行为,我们测得了整体 5.13% 的性能提升,在某些情况下甚至更高。
出于可移植性和易用性的考虑,Redis 假定支持的操作系统发行版上使用的是默认的 GCC 编译器。项目的早期阶段就存在的构建编译器标志多年来一直相当保守,默认优化级别为 -O2。这些标志一直工作得很好。它们提供了稳定的结果和良好的性能,同时没有显著增加 Redis 服务器二进制文件的编译时间或代码大小。没有人想在编译上花费大量时间!
然而,我们可以做得更好一点吗?我们是优化专家。我们总是会问这个问题!
在这篇文章中,我们描述了通过改变编译器版本(GCC 9.4、GCC 11 和 Clang-14)和编译器标志优化对 Redis 性能影响的分析。我们使用性能自动化来评估编译器和标志对性能的影响,这在我们关于Redis/Intel 性能测试、剖析和分析基准规范介绍的文章中讨论过。这项工作的成果是,我们更新了 Redis 的默认编译器标志,因为它们确保了更好的性能。
让我们比较两种流行的开源编译器:GNU 编译器 (GCC) 和 Clang/LLVM。
GCC 是一款经典的优化编译器。它作为 C/C++ 编译器而闻名,但也有其他语言的前端,包括 Fortran、Objective-C 和 Java。GCC 是 GNU 工具链的关键组件,与 make、glibc、gdb 等一起在 Linux 内核开发中扮演着重要角色。
GCC 是许多类 Unix 操作系统(包括大多数 Linux 发行版)的默认编译器。作为开源产品,GCC 由许多人和组织开发,Intel 也是其中之一。
为了进行实验,我们选择了两个 GCC 版本
Clang/LLVM,或简称为 Clang 编译器,是 Clang 前端和 LLVM 后端的组合。Clang 将源代码翻译成 LLVM 字节码,LLVM 框架执行代码生成和优化。Clang 与 GCC 兼容,定位为编译快速、内存占用低、用户友好且具有表达力强的诊断信息。目前,Clang 是 Google Chrome、FreeBSD 和 Apple macOS 的默认编译器。
Clang 的强大之处很大程度上在于 LLVM 社区,许多 IT 公司和个人开发者都参与其中。特别是,Intel 的开发者是活跃的社区贡献者。Intel ICX 编译器基于 LLVM 后端,Intel 将对 LLVM 的增强贡献回馈给社区。
为了进行性能测试,我们选择了实验时可用的最新主要版本,Clang 14。
编译器有数百个配置设置和标志,开发者可以切换这些设置和标志来控制编译器的运行方式以及它生成的代码。这些设置和标志会影响性能优化、代码大小、错误检查以及发出的诊断信息。虽然复制和粘贴默认设置很常见,但调整它们可以产生很大的不同。巨大的不同。
-O2 是一种基本的性能优化,可以同时提高性能和编译时间。-O2 优化了字符串操作(-foptimize-strlen),包含简单的循环优化(例如 -floops-align 和 -ffinite-loops)以及部分内联(-fpartial-inlining)。 这个优化级别包括循环和基本块树的向量化,采用非常廉价的成本模型(very-cheap 允许向量化,如果向量代码可以完全替代正在向量化的标量代码)。关于成本模型和 -O2 内部其他优化标志的更多信息可以在编译器的优化标志列表中找到。
-O2 一直是 Redis 源代码的默认优化级别。传统上,我们将 -O2 用作基准。在项目中,我们将其与更积极的优化选项进行了比较。
-O3 是一种更积极的编译器优化标志。它包含了所有 -O2 的优化行为以及额外的循环优化,例如循环展开 (-floop-unroll-and-jam)。如果循环的一部分始终为真而另一部分为假,它也会分割循环 (-fsplit-loops)。在 -O3 中,非常廉价的成本模型被更精确、动态的成本模型取代,并带有额外的运行时检查。通过这种改变,整个应用程序可以更快,因为编译器定义了代码的哪些部分很慢(例如慢速标量循环)并对其进行优化,从而使本来不错的部分变得更好。
这些 -O3 标志不仅限于这些选项。有关完整列表,请参阅控制优化的编译器选项。
有几点需要记住
-flto 标志代表链接时优化(LTO)。这种优化是在编译器链接应用程序代码时执行的。如果没有此选项,每个文件都会单独优化,然后链接在一起。使用 LTO,文件首先被连接,然后应用优化;这可以提高优化质量。
当应用程序文件之间有大量连接时,此选项非常有用。例如,假设您在一个文件中定义了一个函数。您可能在其他文件中使用或不使用该函数。编译器链接器在构建可执行文件时会利用此知识。使用的函数会被内联(这会使应用程序运行得更快);未使用的函数会从生成的二进制文件中排除。
当您使用 LTO 编译选项时,LTO 有助于消除死代码和始终为 TRUE 或 FALSE 的条件(“巧克力好吃吗?废话!”)。代码中使用的全局变量也会被内联。
所有这些变化都对构建的二进制文件的执行时间产生积极影响。换句话说:我们让 Redis 运行得更快。
我们使用性能自动化框架进行了实验,该框架包含 50 个测试用例。我们的目标是在 Redis 使用模型中实现良好的覆盖率,并确保我们不会对重要用例的性能产生不利影响。
使用自动化框架,Redis 和 Intel 的联合团队创建了多个构建变体,以代表要评估的编译器组合(gcc v9.4、gcc v11 和 clang v14)和编译器标志。除了 Redis,我们还通过在构建阶段使用 REDIS_FLAGS 或仅使用 FLAGS 选项,对 Redis 依赖项(例如 jemalloc 和 lua)的编译器和标志进行了实验。总共,这些变体给了我们 24 个不同的二进制文件或构建变体。
为了评估每个构建变体的有效性,我们使用特定构建变体(例如 Clang 14 + “O3”)构建的 Redis 服务器运行了所有 50 个测试用例。对于这些测试,我们计算了重复运行三次的几何平均值(或简称 geomean)(人人都喜欢可重复的结果!)并计算了三次运行的平均值。这个最终数字,以每秒操作数(ops/sec)表示,指示了构建配置的性能。通过对每对编译器和选项执行这些步骤,我们得到了一组数字进行比较。下面我们展示了它们与基准之间差异的百分比。
我们在四台基于 Intel Xeon Platinum 8360Y 处理器服务器上进行了这些测试。
图 2 总结了我们的实验结果。作为基准,我们使用 GCC 9.4 和默认优化标志编译了 Redis。我们根据所有运行的平均 ops/sec 和 50 个测试用例的 geomean 来判断成功,这些结果是通过使用特定构建变体构建的 redis-server 获得的。每秒操作数越多越好,这表明 Redis 服务器越快。
总的来说,GCC 9.4 O3 + flto 提供了最佳性能,相对于基准,其 geomean 提速分别为 5.19%(包含依赖项)和 5.13%(不包含依赖项)。
在某些用例中,编译器和标志的影响更为显著(参见图 3)。例如,使用 GCC 9.4 -O3 -flto 时,性能相对于基准没有下降,并且有四个测试提高了 10% 以上。
换句话说,结果差异很大——这表明改变 Redis 的优化标志可以带来显著的性能差异。
在其他配置中,一些测试表现得比基准差。然而,有些测试比基准提升了 20% 以上。这是因为 O3 标志启用了一系列积极的优化技术来提高效率。结果是编译器可以重新排序指令并对代码进行其他更改。虽然这些优化通常是有益的,但在某些情况下它们也可能导致代码运行得更慢,特别是如果它们引入了额外的开销或者使代码对缓存不友好。
简而言之,改变这些标志会影响 OSS Redis 的执行速度。
根据本实验结果,Redis 核心团队批准了我们将默认标志更新为 -O3 -flto 的提案(PR 11207)。此配置在所有测量用例中显示 geomean 提高了 5.13%,并且没有出现性能下降的测试。
我们在调优编译器方面的工作尚未结束。我们还有进一步提高 Redis 运行速度的机会。例如:
想看看这一切在软件中如何体现吗?免费试用 Redis,探索其诸多优势。