原子decref实现之间的区别

时间:2019-05-05 03:55:42

标签: c++ c atomic reference-counting

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

大多数操作在库之间是非常一致的,但是我发现“减少引用计数”操作中出现了令人惊讶的变化。 (请注意,通常,共享和弱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操作,但是当引用计数器尚未达到零时,这会导致不必要的“获取”操作,并且可能会导致性能下降。

但大多数其他人( Boostlibstdc++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);
}

那么问题是:实际差异是什么?

子问题:评论是否正确?特定平台有什么功能(在aarch64上,ldardmb ishld便宜吗?还是ia64?)?在什么条件下可以使用较弱的版本(例如,如果dtor是nop,如果删除器只是free,...)?

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

1 个答案:

答案 0 :(得分:1)

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

  

注意:这里的获取负载是对       在共享指针被破坏的同时       没有其他竞争性引用。

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