【发布时间】:2022-01-04 12:42:22
【问题描述】:
C++ 编译器可以省略或合并分配。但是,如果分配的内存是通过原子操作访问的(即使是宽松的内存顺序),GCC 和 Clang 似乎也无法忽略分配。
// new/delete are elided
uint64_t successfulElision() {
auto ptr = new uint64_t{0};
*ptr = 5;
auto result = *ptr;
delete ptr;
return result;
}
// new/delete are not elided
uint64_t failedElision() {
auto ptr = new uint64_t{0};
atomic_ref<uint64_t> rf(*ptr);
rf.store(5, memory_order_relaxed);
auto result = rf.load(memory_order_relaxed);
delete ptr;
return result;
}
https://godbolt.org/z/sacMdbac5
这是什么原因?这是标准要求的吗?
【问题讨论】:
-
虽然这确实是一个可能错过的优化,但这种优化也不会产生成效。在实际代码中,您使用原子,因为变量在线程之间共享。创建一个原子并仅在一个线程上使用它是没有意义的。这种优化的唯一实际案例是像你这样的人工示例,其中有人创建了一个原子但由于某种奇怪的原因没有在线程之间共享它。
-
我希望在协程 task::promise_type 中存储一个原子
来存储协程的延续。一般的智慧是不要这样做,并在 initial_suspend 处暂停协程,以存储延续而不需要同步。但是,我不相信避免同步有那么有价值,因为在 x86 上获取和释放内存操作几乎没有额外成本。在快速路径上,协程永远不会挂起,并从同一个线程设置延续。在慢速路径上,可能会从另一个线程设置延续 -
我想对这两种方法进行基准测试(通过 atomics 与 initial_suspend 继续),但是当我发现使用原子操作可能会阻止编译器忽略协程分配时,我感到很沮丧。
-
顺便说一句,感谢@RaymondChen 的精彩协程教程!我正在尝试探索和更好地理解协程,它们非常有价值。
-
coroutine_handle::done要求协程已经暂停。这意味着done不需要进行任何同步:如果从多个线程中使用协程,则调用者有责任将done与挂起同步。
标签: c++ gcc clang atomic elision