【问题标题】:Difference between atomic decref implementations原子 deref 实现之间的区别
【发布时间】:2019-05-05 03:55:42
【问题描述】:

我一直在研究原子引用计数的实现。

库之间的大部分操作都非常一致,但我发现“减少引用计数”操作的变化令人惊讶。 (请注意,一般来说,shared decref 和weak decref 之间的唯一区别是调用了哪个on_zero()。例外情况如下所示。)

如果还有其他根据 C11/C++11 模型实现的实现(MSVC 是做什么的?),除了“我们使用 seq_cst 因为我们不知道更好”类型之外,请随时编辑它们在。

大部分示例最初都是 C++,但在这里我将它们重写为 C,内联并规范化为 >= 1 约定:

#include <stdatomic.h>
#include <stddef.h>
typedef struct RefPtr RefPtr;
struct RefPtr {
    _Atomic(size_t) refcount;
};
// calls the destructor and/or calls free
// on a shared_ptr, this also calls decref on the implicit weak_ptr
void on_zero(RefPtr *);

来自Boost intrusive_ptr examplesopenssl

void decref_boost_intrusive_docs(RefPtr *p) {
    if (atomic_fetch_sub_explicit(&p->refcount, 1, memory_order_release) == 1) {
        atomic_thread_fence(memory_order_acquire);
        on_zero(p);
    }
}

可以将 memory_order_acq_rel 用于 fetch_sub 操作,但是当引用计数器尚未达到零时,这会导致不需要的“获取”操作,并且可能会造成性能损失。

但其他大多数 ( Boost, libstdc++, libc++ shared ) 做其他事情:

void decref_common(RefPtr *p) {
    if (atomic_fetch_sub_explicit(&p->refcount, 1, memory_order_acq_rel) == 1)
        on_zero(p);
}

但是 libc++ 可以做到something different for the weak count。奇怪的是,这是在一个外部源文件中:

void decref_libcxx_weak(RefPtr *p) {
    if (atomic_load_explicit(&p->refcount, memory_order_acquire) == 1)
        on_zero(p);
    else
        decref_common(p);
}

那么问题是:实际的区别是什么?

子问题:cmets 错了吗?特定平台做什么(在 aarch64 上,ldar 会比dmb ishld 便宜吗?还有 ia64?)?在什么情况下可以使用较弱的版本(例如,如果 dtor 是 nop,如果删除器只是 free,...)?

另请参阅Atomic Reference CountingWhy is an acquire barrier needed before deleting the data in an atomically reference counted smart pointer?

【问题讨论】:

  • 仅无条件地执行release 和有条件地执行acquire 可能会带来性能优势,但OTOH,我怀疑许多shared_ptrs 只有一个引用,因此分支预测器将是无论如何都要经过训练以推测性地执行acquire。如果atomic_fetch_sub_explicit(acq_rel)有有效的指令,那么组合操作会更好。
  • @EOF 所以你的猜测是大多数shared_ptr被用作重的、相对复杂的、类型被擦除的unique_ptr
  • @curiousguy 我不会感到惊讶。那么为何不?大多数情况下,shared_ptr 的低效率不会成为速度方面的瓶颈,所以在它出现在配置文件中之前,谁在乎呢?
  • @curiousguy 我并不是说你不应该在适当的时候使用unique_ptr,我是说它可能不应该 一直被使用,而且它可能在大多数情况下并不重要。
  • @JeremyFriesner 当然,创建的库代码不知道多个所有者或单个所有者是否适合用户,这正是它应该始终返回 unique_ptr 的原因。

标签: c++ c atomic reference-counting memory-model


【解决方案1】:

libc++ 的选择记录在源代码中:

注意:这里的获取负载是对非常 共享指针被破坏的常见情况 没有其他有争议的引用。

libc++ 编码器观察到大多数时候,当最后一个shared_ptr 被销毁时,没有weak_ptr 引用共享对象。据我所知,至少在 x86 上,读-修改-写指令比读指令更广泛。因此,对于最常见的情况,他们决定避免执行扩展且无用的读取-修改-写入。标准库的其他实现不执行此优化。

【讨论】:

  • 嗯,但该代码适用于 all 弱指针,如果它被内联,则可以避免额外的测试。另外,cmets 撒谎。
  • @o11c 据我所知,当一组表达式涉及原子表达式时,优化实际上会被禁用。
  • @curiousguy 确实,为了好玩,看看那个恐怖:godbolt.org/z/yQ35Fp !!!!!!原子被视为易失原子。原子负载的概念和副作用的概念之间存在混淆。标准对此很清楚。仍然有人鼓吹不稳定的原子没有意义。而且这些说教持续了很长时间,我们可以预期这种代码悲观化将一直存在!
  • @Oliv 迷人的汇编。谁提出了这些规则?
  • @Oliv 如果这是一个 acquire 加载,那么删除它是不合法的,对吧?
猜你喜欢
  • 2014-08-01
  • 2012-11-15
  • 1970-01-01
  • 2011-03-20
  • 2016-09-14
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
  • 2017-12-24
相关资源
最近更新 更多