#include <atomic>
std::atomic<int> y(0);
void f() {
auto order = std::memory_order_relaxed;
y.store(1, order);
y.store(1, order);
y.store(1, order);
}
如果变量是易变的,那么显然没有任何优化是适用的。在我的情况下是什么阻止了它?
下面是编译器资源管理器中的代码。
C++11/C++14标准确实允许将三个存储区折叠/合并为最终值的一个存储区。即使在这种情况下:
y.store(1, order);
y.store(2, order);
y.store(3, order); // inlining + constant-folding could produce this in real code
该标准并不保证在y
(带有原子加载或CAS)上旋转的观察者会看到y==2
。依赖于此的程序将有数据竞争bug,但只有普通bug类型的竞争,而不是C++未定义行为类型的数据竞争。(它是UB,只有非原子变量)。一个期望有时看到它的程序甚至不一定是错误的。(参见下面的进度条。)
C++抽象机器上可能的任何排序都可以(在编译时)作为总是发生的排序。这就是行动中的好像规则。在本例中,就好像所有三个存储都是按照全局顺序背靠背发生的,在y=1
和y=3
之间没有来自其他线程的加载或存储。
这是一个实现质量的问题,可以改变实际硬件上观察到的性能/行为。
最明显的问题是进度条。将存储从循环(不包含其他原子操作)中下沉并将它们全部折叠成一个,将导致进度条保持为0,然后在结束时变为100%。
C++11std::atomain
没有办法阻止它们在您不需要的情况下执行此操作,所以目前编译器只需选择从不将多个原子操作合并为一个操作。(将它们合并到一个操作中不会改变它们相对于彼此的顺序。)
但是,在某些情况下,它会非常有帮助,例如,避免循环中无用的shared_ptr
ref count inc/dec。
显然,任何重新排序或合并都不能违反任何其他排序规则。例如,num++;num--;
仍然是运行时和编译时重新排序的完全障碍,即使它不再触及num
的内存。
目前正在讨论如何扩展std::Atomic
API,使程序员能够控制这样的优化,这时编译器将能够在有用的时候进行优化,即使是在精心编写的并非故意低效的代码中也可以进行优化。以下工作组讨论/提案链接中提到了一些有益的优化案例:
在当前标准中,volatile Atomic
将是确保不允许对其存储进行优化的一种方法。(正如Herb Sutter在SO回答中指出的那样,volatile
和Atomic
已经共享了一些需求,但它们是不同的)。另请参见std::memory_order
与CPPreference上volatile
的关系。
不允许优化对volatile
对象的访问(例如,因为它们可能是内存映射的IO寄存器)。
使用volatile atomain
主要解决了进度条问题,但是这有点难看,如果C++决定使用不同的语法来控制优化,那么几年后可能会看起来很傻,这样编译器就可以在实践中开始这样做了。
我认为我们可以相信编译器不会开始进行这种优化,直到有一种方法来控制它。希望它是某种选择(比如memory_order_release_coalesce
),在编译为C++时不会改变现有代码C++11/14代码的行为。但它可能与WG21/P0062中的建议类似:tag don-optimized cases with[[brittle_atomaby]]
。
WG21/P0062警告说,即使volatile atomain
也不能解决所有问题,并不鼓励将其用于此目的。它给出了这个例子:
if(x) {
foo();
y.store(0);
} else {
bar();
y.store(0); // release a lock before a long-running loop
for() {...} // loop contains no atomics or volatiles
}
// A compiler can merge the stores into a y.store(0) here.
即使使用volatile Atomic
,也允许编译器将y.store()
从if/else
中取出,并且只执行一次,因为它仍然使用相同的值执行1个存储。(这将在else分支的长循环之后)。尤其是如果存储区只有replace
或release
而不是seq_cst
。
volatile
确实停止了问题中讨论的合并,但这指出了Atomic<>
上的其他优化对于实际性能也有问题。
没有优化的其他原因包括:没有人编写复杂的代码来允许编译器安全地进行这些优化(而不会出错)。这是不够的,因为N4455说LLVM已经实现或可以很容易地实现它提到的几个优化。
不过,让程序员困惑的理由当然是合理的。一开始,无锁代码就很难正确地编写。
在过去的几个月里,我一直在学习C语言并使用终端。我的代码使用g和c11编译并运行得很好,但在过去几天里它开始出现错误,此后我在编译时遇到了问题。我唯一可以编译和运行的程序依赖于旧的C标准。 我第一次遇到的错误包括 尝试使用ecg$g-o stoi_试验stoi_试验编译。cpp-std=c 11 大堆cpp:13:22:错误:命名空间“std”中没有名为“stoi”的成员;你是说“阿托伊”吗?in
我知道是一个原子对象。但是原子化到什么程度呢?据我所知,操作可以是原子的。使一个对象原子化到底是什么意思?例如,如果有两个线程同时执行以下代码: 那么整个操作(例如)是原子操作吗?还是对变量atomic(so)进行了更改?
考虑以下代码:
上面的代码使用不同的编译器会有不同的结果。这是编译器的错误还是我漏掉了什么? 叮叮当当 1 1 (https://godbolt.org/z/s43T55rxq) msvc 1 1 (https://godbolt.org/z/YnKfKh41q) 全球循环 0 1 (https://godbolt.org/z/91xdfv93c)
问题内容: 我正在尝试使用泛型实现以下结构。收到编译器错误,无法找出原因。 这个想法是译者使用T作为字典中键的类型。例如,可以是字符串或枚举。子类提供具体的字典。 但是它失败,因为:“类型’String’不符合协议’Hashable’” 但是String符合Hashable。它也不适用于Int,后者也符合Hashable。 如果删除类型约束,则仅用于测试(在此我还必须禁用字典,因为我不能在其中使用
我有一个简单的测试设置,如 但当我尝试编译测试时,我会遇到53个错误,比如 实际上并没有传达任何关于问题所在的有用信息。我只能假设在我的构建中没有正确配置某些内容。sbt文件或其他地方。 这段代码确实曾经工作过,在我清理东西的过程中,事情发生了变化,现在它被破坏了,没有好的诊断。 有人能提出要找的东西吗?