o11*_*11c 5 c c++ atomic reference-counting memory-model
我一直在研究原子引用计数的实现。
大多数操作在库之间是非常一致的,但是我发现“减少引用计数”操作令人吃惊。(请注意,通常,共享和弱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 *);
Run Code Online (Sandbox Code Playgroud)
从Boost intrusive_ptr示例和openssl:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
可以对fetch_sub操作使用memory_order_acq_rel,但是当引用计数器尚未达到零时,这会导致不必要的“获取”操作,并且可能会导致性能下降。
但大多数其他工具( 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);
}
Run Code Online (Sandbox Code Playgroud)
但是libc ++ 对于弱项做了一些不同的事情。奇怪的是,这是在外部源文件中:
void decref_libcxx_weak(RefPtr *p) {
if (atomic_load_explicit(&p->refcount, memory_order_acquire) == 1)
on_zero(p);
else
decref_common(p);
}
Run Code Online (Sandbox Code Playgroud)
那么问题是:实际差异是什么?
子问题:评论是否正确?特定平台有什么功能(在aarch64上,ldar比dmb ishldia64还便宜?)?在什么情况下可以使用较弱的版本(例如,如果dtor是nop,如果删除器是free,...)?
libc++ 选择记录在源代码中:
注意:此处的获取加载是对非常常见情况的优化,其中共享指针在没有其他竞争引用的情况下被破坏。
libc++ 编码器观察到,大多数时候,当最后一个shared_ptr被销毁时,没有weak_ptr引用共享对象。据我所知,至少在 x86 上,读-修改-写指令比读指令要广泛得多。因此,对于最常见的情况,他们决定避免执行广泛且无用的读取-修改-写入。标准库的其他实现不执行此优化。