c++ - 联盟中的atomic <>作为性能黑客

标签 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_txchglatomic相对应。我现在正在考虑以下(可能是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
 }

最佳答案

首先,在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>,它“拥有”动态创建的对象,以及所有已“链接”到该对象的shared_ptr的引用计数器。 shared_ptr<T>具有指向Node<T>的obj指针的“阴影”指针。

这是基本的设计布局。原子增量/减量是通过取消引用完成的,这几乎与您认为从切换到非原子增量/减量计数器所消除的开销一样多。

我假设目标是允许可以在非线程工作中使用的非原子引用计数所有权,这样就不必同步引用计数器。

请注意,该选择不是在shared_ptr内,而是在Node内。

此外,最近的C++ 11/C++ 14库中的boost::shared_ptr采用了最重要的性能增强,体现在模板函数make_shared中。

该概念基于以下观察结果:分配此结构意味着至少有两个分配,一个分配给使用new创建的对象,另一个分配给将拥有它的Node(假定shared_ptr位于堆栈或成员上)。 。因此,存在一个通过make_shared函数在boost(然后由C++ 11采用)中实现的设计,该设计对Node和要创建的对象都进行一次分配,该分配使用“就地构造”并破坏了对象。被管理的对象(所有这些中的T)。这种优化可能至少与性能增强以及内存效率优化同等重要,除非您也选择实现,否则您将松懈。

还要注意的是,兼容的C++ 11/C++ 14版本现在提供了(某种类型的)垃圾收集,因此,在继续操作之前,应询问是否已检查并拒绝该垃圾收集。

假设被拒绝,您可能会询问 union 的问题,但是另一种方法可能会提供您尚未考虑的可能性和灵活性。

使用基于策略的设计,您可以通过根据参数构建模板类来创建编译时多态选项。基本思想是从模板类中的参数之一派生。例如:
class AtomicRefCount
{ ...
};

class NonAtomicRefCount
{ ...
};

template< typename T, typename Ref >
class Node : public Ref
{ ...
};

typedef Node< SomeStruct, AtomicRefCount >  SomeStruct_Node;

这个想法是能够基于模板的参数选择行为或构造选项。可以嵌套它们以深化此概念。在此示例中,想法是基于原子或非原子引用计数整数类型的选项创建节点。

但是,挑战在于这意味着这两种节点是不同的类型。它们不再是“Node ”。它们是'Node '类型,因此shared_ptr<T>无法理解它们,它们必须是shared_ptr< T, Ref >
但是,使用此技术,可以从模板接口(interface)声明的公共(public)基础来设计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 >一样,但是其中嵌入了一些策略。

查看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是采用弱类型的策略构建的,则它是“weak_ptr”。如果采用“强”类型的策略创建,则为shared_ptr。该差异在此库中称为附件策略,并且恰好是派生层次结构的根类。附件策略和MetaPtr之间是锁定策略。这两个是储物柜,即StrongLocking和WeakLocking。有非储物柜,StrongNoLock和WeakNoLock。

可以从几个小模板类中构建几种不同类型的智能指针,这些类实现不少于6种不同类型的智能指针,它们均基于相同的接口(interface)并共享许多代码。

基于策略的设计是实现所需内容的一种方法,而无需求助于 union ,尽管这不是一个坏选择。这很简单,但是如果您在设计中打算使用更多选项,则应考虑基于策略的设计。

有关基于策略的智能指针设计的更多信息,请参见Alexandrescu于2001年撰写的书,其中他介绍了基于策略的智能指针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)

其中许多是可以多种组合选择的,所有版本均提供弱版本和标准版本。

关于c++ - 联盟中的atomic <>作为性能黑客,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32773186/

相关文章:

c++ - 内存重用和UB

c++ - 在 VS Express 中添加文件

c - 如果在调用 pthread_cond_wait() 之后,另一个线程获取了被锁定的互斥锁,然后调用 phread_cond_broadcast 会发生什么情况?

c++ - enable_shared_from_this - 空的内部弱指针?

c++ - 为什么共享指针赋值是 'swap' ?

c++ - 使用 boost::make_shared 分配 char* 数组

当我在 Visual Studio 中运行程序时,C++ 随机数生成器通常会在 2 次后停止生成数字

c++ - Ubuntu OpenCV 不编译

python - 如何在 "background"中运行部分脚本(单个函数)?

java - 跨线程同步