gpt4 book ai didi

c++ - 在 C++ 中强制执行语句顺序

转载 作者:IT老高 更新时间:2023-10-28 11:55:26 25 4
gpt4 key购买 nike

假设我有许多要执行的语句
一个固定的顺序。我想使用优化级别为 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;

最佳答案

在与 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对实现完全可见。我还提取了一个(非可移植)版本的 DoNotOptimize来自谷歌基准库,你可以在这里找到:https://github.com/google/benchmark/blob/master/include/benchmark/benchmark_api.h#L208

#include <chrono>

template <class T>
__attribute__((always_inline)) inline void DoNotOptimize(const T &value) {
asm volatile("" : "+m"(const_cast<T &>(value)));
}

// The compiler has full knowledge of the implementation.
static int foo(int x) { return x * 2; }

auto time_foo() {
using Clock = std::chrono::high_resolution_clock;

auto input = 42;

auto t1 = Clock::now(); // Statement 1
DoNotOptimize(input);
auto output = foo(input); // Statement 2
DoNotOptimize(output);
auto t2 = Clock::now(); // Statement 3

return t2 - t1;
}

在这里,我们确保输入数据和输出数据在计算 foo 周围标记为不可优化。 ,并且仅在这些标记周围计算时间。由于您使用数据来钳制计算,因此可以保证保持在两个时间之间,但允许优化计算本身。最近构建的 Clang/LLVM 生成的 x86-64 程序集是:
% ./bin/clang++ -std=c++14 -c -S -o - so.cpp -O3
.text
.file "so.cpp"
.globl _Z8time_foov
.p2align 4, 0x90
.type _Z8time_foov,@function
_Z8time_foov: # @_Z8time_foov
.cfi_startproc
# BB#0: # %entry
pushq %rbx
.Ltmp0:
.cfi_def_cfa_offset 16
subq $16, %rsp
.Ltmp1:
.cfi_def_cfa_offset 32
.Ltmp2:
.cfi_offset %rbx, -16
movl $42, 8(%rsp)
callq _ZNSt6chrono3_V212system_clock3nowEv
movq %rax, %rbx
#APP
#NO_APP
movl 8(%rsp), %eax
addl %eax, %eax # This is "foo"!
movl %eax, 12(%rsp)
#APP
#NO_APP
callq _ZNSt6chrono3_V212system_clock3nowEv
subq %rbx, %rax
addq $16, %rsp
popq %rbx
retq
.Lfunc_end0:
.size _Z8time_foov, .Lfunc_end0-_Z8time_foov
.cfi_endproc


.ident "clang version 3.9.0 (trunk 273389) (llvm/trunk 273380)"
.section ".note.GNU-stack","",@progbits

在这里你可以看到编译器优化了对 foo(input) 的调用。直到一条指令, addl %eax, %eax ,但没有将它移到时间之外或在不断输入的情况下完全消除它。

希望这会有所帮助,C++ 标准委员会正在研究标准化类似于 DoNotOptimize 的 API 的可能性。这里。

关于c++ - 在 C++ 中强制执行语句顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37786547/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com