假设我有许多要按固定顺序执行的语句。我想使用优化级别 2 的 g++,因此可以重新排序一些语句。有什么工具可以强制执行一定的语句顺序?
考虑以下示例。
using Clock = std::chrono::high_resolution_clock;
auto t1 = Clock::now(); // Statement 1
foo(); // Statement 2
auto t2 = Clock::now(); // Statement 3
auto elapsedTime = t2 - t1;
在此示例中,重要的是语句 1-3 按给定顺序执行。但是,编译器不能认为语句2独立于1和3并执行如下代码吗?
using Clock=std::chrono::high_resolution_clock;
foo(); // Statement 2
auto t1 = Clock::now(); // Statement 1
auto t2 = Clock::now(); // Statement 3
auto elapsedTime = t2 - t1;
原文由 S2108887 发布,翻译遵循 CC BY-SA 4.0 许可协议
在与 C++ 标准委员会讨论后,我想尝试提供一个更全面的答案。除了是 C++ 委员会的成员之外,我还是 LLVM 和 Clang 编译器的开发人员。
从根本上说,没有办法在序列中使用屏障或某些操作来实现这些转换。基本问题是整数加法之类的操作语义对于实现来说是 完全已知 的。它可以模拟它们,它知道它们不能被正确的程序观察到,并且总是可以自由地移动它们。
我们可以尝试阻止这种情况,但它会产生极其负面的结果,最终会失败。
首先,在编译器中防止这种情况的唯一方法是告诉它所有这些基本操作都是可观察的。问题是这会排除绝大多数编译器优化。在编译器内部,我们基本上没有很好的机制来模拟 时间 是可观察的,但没有别的。我们甚至没有一个很好的模型来 _说明哪些操作需要时间_。例如,将 32 位无符号整数转换为 64 位无符号整数需要时间吗?在 x86-64 上花费零时间,但在其他架构上花费非零时间。这里没有普遍正确的答案。
但是,即使我们通过一些英勇的行为成功地阻止了编译器对这些操作进行重新排序,也不能保证这样做就足够了。考虑一种在 x86 机器上执行 C++ 程序的有效且合规的方法:DynamoRIO。这是一个动态评估程序机器代码的系统。它可以做的一件事是在线优化,它甚至能够在时序之外推测性地执行整个基本算术指令范围。而且这种行为并不是动态评估器所独有的,实际的 x86 CPU 也会推测(数量少得多)指令并动态重新排序它们。
基本的认识是,算术是不可观察的(即使在时序级别)这一事实渗透到计算机的各个层。对于编译器、运行时甚至硬件来说都是如此。强制它是可观察的既会极大地限制编译器,也会极大地限制硬件。
但所有这一切都不应该让你失去希望。当您想为基本数学运算的执行计时时,我们已经深入研究了可靠工作的技术。通常在进行 微基准测试 时使用这些。我在 CppCon2015 上讨论过这个问题: https ://youtu.be/nXaxk27zwlk
那里显示的技术也由各种微基准库提供,例如 Google 的: https ://github.com/google/benchmark#preventing-optimization
这些技术的关键是关注数据。您使计算的输入对优化器不透明,而计算的结果对优化器不透明。一旦你这样做了,你就可以可靠地计时。让我们看一下原始问题中示例的实际版本,但
foo
的定义对实现完全可见。我还从您可以在此处找到的 Google Benchmark 库中提取了DoNotOptimize
的(非便携式)版本: https ://github.com/google/benchmark/blob/v1.0.0/include /benchmark/benchmark_api.h#L208在这里,我们确保输入数据和输出数据在计算
foo
周围被标记为不可优化,并且只有在这些标记周围才计算计时。因为您使用数据来钳制计算,所以可以保证保持在两个时间之间,但允许优化计算本身。最近构建的 Clang/LLVM 生成的 x86-64 程序集是:在这里,您可以看到编译器将对
foo(input)
的调用优化为一条指令addl %eax, %eax
,但不会将其移出时序或完全消除它,尽管输入是恒定的。希望这会有所帮助,C++ 标准委员会正在研究标准化类似于
DoNotOptimize
的 API 的可能性。