【发布时间】: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 examples 和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);
}
}
可以将 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 Counting 和 Why 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