quick-bench.com 的基准测试在禁用优化的情况下要快得多
Benchmark on quick-bench.com is much faster with disabled optimizations
我创建了一个非常简单的基准来说明 短字符串优化 和 运行 quick-bench.com。对于 SSO-disabled/enabled 字符串 class 的比较,基准测试非常有效,结果与 GCC 和 Clang 非常一致。但是,我意识到当我禁用优化时,报告的时间比启用优化(-O2
或 -O3
)时观察到的时间快 4 倍左右,GCC 和 Clang 都是如此。
基准在这里:http://quick-bench.com/DX2G2AdxUb7sGPE-zLRa41-MCk0。
知道什么可能导致未优化的基准测试 运行 快 4 倍吗?
很遗憾,我看不到生成的程序集;不知道问题出在哪里(选中了 "Record disassembly" 框,但在我的 运行 中没有任何效果)。此外,当我 运行 在本地使用 Google Benchmark 进行基准测试时,结果符合预期,即优化的基准测试 运行s 更快。
我还尝试在 Compiler Explorer 中比较这两种变体,未优化的变体似乎执行了更多的指令:https://godbolt.org/z/I4a171.
因此,正如评论中所讨论的那样,问题在于 quick-bench.com 并未显示基准测试代码的绝对时间,而是相对于无操作基准测试所用时间的时间。空操作基准可以在 quick-bench.com:
的源文件中找到
static void Noop(benchmark::State& state) {
for (auto _ : state) benchmark::DoNotOptimize(0);
}
一个运行的所有基准都编译在一起。因此优化标志也适用于它。
Reproducing and comparing the no-op benchmark for different optimization levels 可以看到,从 -O0
到 -O1
版本大约有 6 到 7 倍的加速。在比较使用不同优化标志完成的基准 运行 时,必须考虑基线中的这个因素来比较结果。因此,在问题的基准测试中观察到的 4 倍加速不仅仅是补偿,而且行为确实符合人们的预期。
-O0
和 -O1
之间的空操作编译的一个主要区别是 -O0
在 google 中有一些断言和其他附加分支-基准代码,针对更高的优化级别进行了优化。
此外,在 -O0
循环的每次迭代都会多次加载到寄存器、修改和存储到 state
的内存部分,例如用于递减循环计数器和循环计数器上的条件,而 -O1
版本将在寄存器中保留 state
,从而使循环中的内存 load/stores 变得不必要。前者要慢得多,每次迭代至少需要几个周期来进行必要的存储转发 and/or 从内存中重新加载。
我创建了一个非常简单的基准来说明 短字符串优化 和 运行 quick-bench.com。对于 SSO-disabled/enabled 字符串 class 的比较,基准测试非常有效,结果与 GCC 和 Clang 非常一致。但是,我意识到当我禁用优化时,报告的时间比启用优化(-O2
或 -O3
)时观察到的时间快 4 倍左右,GCC 和 Clang 都是如此。
基准在这里:http://quick-bench.com/DX2G2AdxUb7sGPE-zLRa41-MCk0。
知道什么可能导致未优化的基准测试 运行 快 4 倍吗?
很遗憾,我看不到生成的程序集;不知道问题出在哪里(选中了 "Record disassembly" 框,但在我的 运行 中没有任何效果)。此外,当我 运行 在本地使用 Google Benchmark 进行基准测试时,结果符合预期,即优化的基准测试 运行s 更快。
我还尝试在 Compiler Explorer 中比较这两种变体,未优化的变体似乎执行了更多的指令:https://godbolt.org/z/I4a171.
因此,正如评论中所讨论的那样,问题在于 quick-bench.com 并未显示基准测试代码的绝对时间,而是相对于无操作基准测试所用时间的时间。空操作基准可以在 quick-bench.com:
的源文件中找到static void Noop(benchmark::State& state) {
for (auto _ : state) benchmark::DoNotOptimize(0);
}
一个运行的所有基准都编译在一起。因此优化标志也适用于它。
Reproducing and comparing the no-op benchmark for different optimization levels 可以看到,从 -O0
到 -O1
版本大约有 6 到 7 倍的加速。在比较使用不同优化标志完成的基准 运行 时,必须考虑基线中的这个因素来比较结果。因此,在问题的基准测试中观察到的 4 倍加速不仅仅是补偿,而且行为确实符合人们的预期。
-O0
和 -O1
之间的空操作编译的一个主要区别是 -O0
在 google 中有一些断言和其他附加分支-基准代码,针对更高的优化级别进行了优化。
此外,在 -O0
循环的每次迭代都会多次加载到寄存器、修改和存储到 state
的内存部分,例如用于递减循环计数器和循环计数器上的条件,而 -O1
版本将在寄存器中保留 state
,从而使循环中的内存 load/stores 变得不必要。前者要慢得多,每次迭代至少需要几个周期来进行必要的存储转发 and/or 从内存中重新加载。