我正在考虑将自制的共享指针实现为垃圾收集器的一部分,以避免std::shared_ptr
内部原子的(可能是轻微的)开销。松散地等同于:
template <typename T>
struct my_atomic_shared
{
std::atomic<std::size_t> refcount;
std::unique_ptr<T> value;
};
我的简要希望是小整数和atomic<small integers>
是等价的,但倾销asm显示mov
对std::size_t
xchgl
atomic
。我现在正在考虑以下,可能是UB调用,实现:
template <typename T>
struct my_normal_shared
{
std::size_t refcount;
std::unique_ptr<T> value;
};
template <typename T>
struct my_shared
{
// Surprisingly tedious constructor/destructor definition omitted
enum {ATOMIC, NORMAL} tag;
union
{
my_atomic_shared<T> atomic;
my_normal_shared<T> normal;
} value;
void promote()
{
// pseudocode. Sequential consistency, calls should go via store/load
if(tag == NORMAL)
{
T got = *(value.normal);
*(value.atomic) = got;
tag = ATOMIC;
}
}
};
前提是默认情况下创建非原子版本,并在将promote()
的实例传递给另一个线程之前调用my_shared<>
。
我想知道是否有更好的方法来实现这种性能攻击。我也有兴趣知道是否有理由将atomic<>
变量放入工会是注定要失败的。
编辑:添加较少不连贯的伪代码。存储的类型比用于引用计数的类型更有趣。
template <typename T>
struct my_shared_state
{
enum {ATOMIC, NORMAL} tag;
union
{
std::atomic<std::size_t> atomic;
std::size_t normal;
} refcount;
T value;
void incr();
void decr();
}
template <typename T>
struct my_shared_pointer
{
my_shared_state<T> * state;
// ctors and dtors modify refcount held in state
}
答案 0 :(得分:3)
首先,在shared_ptr和其他引用计数指针设计中,引用计数不在shared_ptr类中。它位于共享指针指向的节点中。
因此关系是:
template<typename T>
class Node
{
std::atomic<unsigned int> RefCount;
T * obj;
};
template<typename T>
class shared_ptr
{
Node<T> * node;
T * shadow;
};
这不是shared_ptr的代码,而是数据结构布局的伪代码。 shared_ptr指向Node<T>
,其中&#34;拥有&#34;动态创建的对象,以及所有具有&#34;链接&#34;的shared_ptr的引用计数器。它。 shared_ptr<T>
有一个&#34;影子&#34;指向Node<T>
的obj指针的指针。
这是基本的设计布局。原子递增/递减是通过取消引用来完成的,这几乎与您可能认为从切换到非原子递增/递减计数器时所消除的开销一样多。
我假设目标是允许非原子引用计数可用于非线程工作的所有权,这样引用计数器不需要同步。
请注意,该选项不在shared_ptr中,而是在Node内。
此外,最近的C ++ 11 / C ++ 14库中的boost :: shared_ptr采用了最重要的性能增强,体现在模板函数make_shared中。
这个概念基于这样的观察:分配这个结构意味着至少有两个分配,一个用于使用new创建的对象,另一个用于拥有它的Node(shared_ptr被假定在堆栈中,或者成员)。因此,有一个设计在boost(然后由C ++ 11采用)中通过make_shared函数实现,该函数为Node和正在创建的对象执行一个分配,它使用&#34;就地构造&#34; ;和被管理对象的破坏(所有这些中的T)。这种优化可能至少同样具有性能提升和内存效率优化,除非您选择同时实现这一优化,否则您将无法进行优化。
同样请注意,垃圾收集(某种类型)现在可以在兼容的C ++ 11 / C ++ 14版本中使用,因此在继续操作之前,应该询问是否已经检查并拒绝这些垃圾收集是不合适的。
假设被拒绝,您可以就工会进行查询,但另一种方法提供了您可能尚未考虑的可能性和灵活性。
使用基于策略的设计,您可以通过从参数构建模板类来创建编译时多态选项。基本思想是从模板类中的一个参数派生。例如:
class AtomicRefCount
{ ...
};
class NonAtomicRefCount
{ ...
};
template< typename T, typename Ref >
class Node : public Ref
{ ...
};
typedef Node< SomeStruct, AtomicRefCount > SomeStruct_Node;
我们的想法是能够根据模板的参数选择行为或构造选项。它们可以嵌套用于这个概念的深层链接。在这个例子中,我们的想法是基于原子或非原子引用计数整数类型的选项创建一个Node。
然而,挑战在于这意味着两种节点是不同的类型。他们不是&#39; Node&lt; T&gt;&#39;了。他们是&#39; Node&lt; T,Ref&gt;&#39;类型,所以shared_ptr<T>
无法理解它们,它们必须是shared_ptr< T, Ref >
然而,使用这种技术,可以从模板接口声明的公共基础设计shared_ptr和weak_ptr,这些行为基于在声明指针时提供的参数而有不同的行为。在我多年前写过的智能指针库中,为了处理包括垃圾收集在内的一些不同问题,这些都是可能的:
template< typename T > struct MetaPtrTypes
{
typedef typename SmartPointers::LockPolicy< T, SmartPointers::StrongAttachmentPolicy > StrongLocking;
typedef typename SmartPointers::NoLockPolicy< T, SmartPointers::WeakAttachmentPolicy > WeakNoLocking;
typedef SmartPointers::LockPolicy< T, SmartPointers::WeakAttachmentPolicy > WeakLocking;
typedef SmartPointers::NoLockPolicy< T, SmartPointers::StrongAttachmentPolicy > StrongNoLocking;
typedef SmartPointers::PublicAccessPolicy< T, StrongNoLocking > PublicStrongNoLock;
typedef SmartPointers::MetaPtr< T, StrongLocking > LPtr;
typedef SmartPointers::MetaPtr< T, WeakNoLocking > WPtr;
typedef SmartPointers::MetaPtr< T, WeakLocking > WLPtr;
typedef SmartPointers::MetaPtr< T, PublicStrongNoLock > MPtr;
typedef T Type;
};
这是针对C ++ 03左右的,因为我们等待C ++ 11的功能从草稿中爬出来。我的想法是创建一个MetaPtrTypes< SomeClass > SomeClassPtrs;
这样做了类似SomeClassPtrs::MPtr
的类型,这是一种shared_ptr。 WPtr是一个弱指针,有点类似于std :: shared_ptr和std :: weak_ptr,但有一些自定义内存分配选项不适用于该期间的shared_ptr,并且具有某些通常需要保护shared_ptr的锁定功能在应用程序中使用互斥锁(因为写入shared_ptr不是线程安全的)。
注意基于策略的设计策略。 MPtr相当于:
typedef std::shared_ptr< SomeClass > SPtr;
无论何时可以使用SPtr,都可以使用MPtr。但看看MPtr是如何塑造的。它是MetaPtr< T, PublicStrongNoLock >
。这是一种建立的政策建构范式。 MetaPtr就像前面提到的Node< T, Ref >
一样,但内置了SEVERAL策略。
纵观MPtr,WPtr,LPtr,您会注意到MetaPtr创建基于各种策略,其中包括StrongLocking和WeakLocking。
他们是:
typedef typename SmartPointers
::LockPolicy< T, SmartPointers
::StrongAttachmentPolicy > StrongLocking;
typedef SmartPointers
::LockPolicy< T, SmartPointers
::WeakAttachmentPolicy > WeakLocking;
这两个策略可以从中制作智能指针。请注意,对于WLPtr,策略是WeakLocking,而对于LPtr,策略是针对StrongLocking的。
它们都是由MetaPtr(主要用户界面类)构成的。如果MetaPtr采用弱政策,那么它就是弱_ptr。如果它采用强有力的政策,那么它就是一个shared_ptr。差异在此库中称为附件策略,恰好是从中派生层次结构的根类。在附件策略和MetaPtr之间是锁定策略。这两个是储物柜,StrongLocking和WeakLocking。有非储物柜,StrongNoLock和WeakNoLock。
几种不同类型的智能指针可以从一些小模板类中构建,这些模板类实现了不少于6种不同类型的智能指针,所有这些都基于相同的接口并共享大部分代码。
基于策略的设计是实现您想要的一种方式,而不需要求助于工会,尽管这不是一个糟糕的选择。这很简单,但如果你的设计中有更多选择,你应该考虑基于策略的设计。
更多关于智能指针的基于策略的设计可以在2001年的Alexandrescu的书中找到,其中他提出了基于策略的智能指针loki。
在所提供的示例中,MetaPtr适用于需要自定义内存分配的许多高性能方案,但该期间的shared_ptr不支持它(并且多年不会这样)。这些选项中,可通过政策选择:
Standard memory allocation
Custom memory allocation
Garbage collection/memory management
Fast locking of writeable pointers
Lightweight reference counted smart pointers
Non-reference counted smart pointers (something like unique_ptr)
Array/Container aware smart pointers
GPU resource managers (loading/unloading textures,models and shader code)
其中许多都可以选择各种组合,所有组合都有弱版和标准版。