【问题标题】:Why do relaxed atomic operations prevent compiler optimizations?为什么宽松的原子操作会阻止编译器优化?
【发布时间】: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


【解决方案1】:

您在某些功能中使用它,因此您不能说它可能会被优化。如果您用外部函数替换原子操作,它将是相同的: https://godbolt.org/z/GsYjrb6z5

【讨论】:

  • 原子操作不是外部函数,它们是内联的。如果我调用内联函数来修改内存,分配仍然被忽略godbolt.org/z/xeaPav7o3
  • 原子函数确实调用编译器内置函数,例如 __atomic_load_n,但编译器内置函数也不是外部函数。如果我调用 __builtin_memcpy 例如godbolt.org/z/Txda3j5ob,分配仍然会被忽略
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-03-08
  • 1970-01-01
  • 1970-01-01
  • 2013-10-16
  • 2020-07-24
相关资源
最近更新 更多