2

我创建了一个非常简单的基准来说明短字符串优化并在quick-bench.com上运行它。该基准测试非常适用于 SSO 禁用/启用的字符串类的比较,结果与 GCC 和 Clang 非常一致。-O2但是,我意识到当我禁用优化时,报告的时间比启用优化 (或)观察到的时间快 4 倍左右-O3,无论是使用 GCC 还是 Clang。

基准测试在这里:http: //quick-bench.com/DX2G2AdxUb7sGPE-zLRa41-MCk0

知道什么可能导致未优化的基准测试运行速度提高 4 倍吗?

不幸的是,我看不到生成的程序集;不知道问题出在哪里(“记录反汇编”框已选中,但对我的运行没有影响)。此外,当我使用 Google Benchmark 在本地运行基准测试时,结果与预期一致,即优化后的基准测试运行得更快。

我还尝试比较 Compiler Explorer 中的两种变体,未优化的变体似乎执行了更多指令:https ://godbolt.org/z/I4a171 。

4

1 回答 1

2

因此,正如评论中所讨论的,问题在于 quick-bench.com 没有显示基准代码的绝对时间,而是相对于无操作基准所用时间的时间。no-op 基准测试可以在 quick-bench.com 的源文件中找到:

static void Noop(benchmark::State& state) {
    for (auto _ : state) benchmark::DoNotOptimize(0);
}

一次运行的所有基准测试都是一起编译的。因此,优化标志也适用于它。

复制和比较不同优化级别的无操作基准-O0可以看到,从to-O1版本大约有 6 到 7 倍的加速。在比较使用不同优化标志完成的基准运行时,必须考虑基线中的这个因素来比较结果。因此,在问题的基准测试中观察到的 4 倍加速得到了充分的补偿,并且行为确实符合人们的预期。

-O0和之间的无操作编译的一个主要区别-O1是,-O0在 google-benchmark 代码中有一些断言和其他附加分支,它们针对更高的优化级别进行了优化。

此外,在-O0循环的每次迭代中,将多次加载到寄存器中,修改并存储到内存中state,例如用于递减循环计数器和循环计数器的条件,而-O1版本将保留state在寄存器中,使内存加载/存储在循环是不必要的。前者要慢得多,每次迭代至少需要几个周期来进行必要的存储转发和/或从内存重新加载。

于 2019-09-30T17:12:14.487 回答