原子<>在一个联盟内作为一个表演黑客

时间:2015-09-25 00:57:37

标签: c++ multithreading shared-ptr atomic

我正在考虑将自制的共享指针实现为垃圾收集器的一部分,以避免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显示movstd::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
 }

1 个答案:

答案 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)

其中许多都可以选择各种组合,所有组合都有弱版和标准版。