使用64位指针进行无锁内存回收

时间:2009-06-30 05:35:09

标签: c++ lock-free

Herlihy和Shavit的书(The Art of Multiprocessor Programming)对内存回收的解决方案使用了Java的AtomicStampedReference<T>;

要用C ++为x86_64写一个,我想要至少需要一个12字节的交换操作 - 对于64位指针为8,对于int为4。

是否有x86硬件支持,如果没有,任何关于如何在没有它的情况下进行无等待内存回收的指针?

7 个答案:

答案 0 :(得分:3)

Windows为您提供了一堆原子的Interlocked functions,可以用来做您想做的事情。其他平台也存在类似的功能,我相信Boost也有一个互锁的库。

你的问题不是很明确,我也没有Herlihy和Shavit的副本。也许如果你详细说明或给出了伪装代码概述你想做什么,我们可以给你一个更具体的答案。

答案 1 :(得分:3)

是的,有硬件支持,但我不知道它是否被C ++库公开。无论如何,如果你不介意做一些低级的不可移植的汇编语言欺骗 - 在英特尔手册中查找CMPXCHG16B指令。

答案 2 :(得分:2)

好的希望,我有这本书,

对于可能提供答案的其他人,重点是实现这个类:

class AtomicReference<T>{
  public:
    void set(T *ref, int stamp){ ... }
    T *get(int *stamp){ ... }
  private:  
    T *_ref; 
    int _stamp; 

};

以无锁方式:

  • set()以原子方式更新引用和戳记。
  • get()返回引用并将* stamp设置为与引用对应的戳记。

JDonner拜托,如果我错了,请纠正我。

现在我的回答:我不认为你可以在没有锁定的情况下做到这一点(锁定可以是while(test_and_set()!= ..))。因此,没有无锁的算法。这意味着可以通过寄存器为任何N建立一个N-lock锁定方式。

如果你看一下pragma 9.8.1这本书,那么AtomicMarkableReference与整数标记的单个位相同。作者建议从指针中“窃取”一点,从单个词中提取标记和指针(几乎引用)这显然意味着他们想要使用单个原子寄存器来完成它。

然而,如果没有它,可能有办法实现无等待内存回收。我不知道。

答案 3 :(得分:2)

是的,x64支持这个;你需要使用CMPXCHG16B。

答案 4 :(得分:2)

您可以依靠指针使用少于64位的事实来节省内存。首先,定义一个compare&set函数(此ASM适用于GCC和ICC):

inline bool CAS_ (volatile uint64_t* mem, uint64_t old_val, uint64_t new_val)
{
  unsigned long old_high = old_val >> 32, old_low = old_val;
  unsigned long new_high = new_val >> 32, new_low = new_val;

  char res = 0;
  asm volatile("lock; cmpxchg8b (%6);"
               "setz %7; "
               : "=a" (old_low),  // 0
                 "=d" (old_high)  // 1
               : "0" (old_low),   // 2
                 "1" (old_high),  // 3
                 "b" (new_low),   // 4
                 "c" (new_high),  // 5
                 "r" (mem),       // 6
                 "m" (res)        // 7
               : "cc", "memory");
  return res;
}

然后,您需要构建标记指针类型。我假设一个40位指针,缓存行宽度为128字节(如Nehalem)。通过减少错误共享,争用等,与缓存行对齐将大大提高速度;在某些情况下,使用很多更多内存有明显的权衡。

template <typename pointer_type, typename tag_type, int PtrAlign=7, int PtrWidth=40>
struct tagged_pointer
{
  static const unsigned int PtrMask = (1 << (PtrWidth - PtrAlign)) - 1;
  static const unsigned int TagMask = ~ PtrMask;

  typedef unsigned long raw_value_type;

  raw_value_type raw_m_;

  tagged_pointer () : raw_m_(0) {}
  tagged_pointer (pointer_type ptr) { this->pack(ptr, 0); }
  tagged_pointer (pointer_type ptr, tag_type tag) { this->pack(ptr, tag); }

  void pack (pointer_type ptr, tag_type tag)
  {
    this->raw_m_ = 0;
    this->raw_m_ |= ((ptr >> PtrAlign) & PtrMask);
    this->raw_m_ |= ((tag << (PtrWidth - PtrAlign)) & TagMask);
  }

  pointer_type ptr () const
  {
    raw_value_type p = (this->raw_m_ & PtrMask) << PtrAlign;
    return *reinterpret_cast<pointer_type*>(&p);
  }

  tag_type tag () const
  {
    raw_value_type t = (this->raw_m_ & TagMask) >> (PtrWidth - PtrAlign_;
    return *reinterpret_cast<tag_type*>(&t);
  }
};

我没有机会调试此代码,所以你需要这样做,但这是一般的想法。

答案 5 :(得分:2)

注意,在x86_64架构和gcc上,您可以启用128位CAS。可以使用-mcx16 gcc选项启用它。

int main()
{
   __int128_t x = 0;
   __sync_bool_compare_and_swap(&x,0,10);
   return 0;
}

编译:

gcc -mcx16 file.c

答案 6 :(得分:1)

cmpxchg16b操作提供了预期的操作,但要注意一些旧的x86-64处理器没有此指令。

然后,您只需要使用计数器和指针以及asm-inline代码构建一个实体。我在这里写了一篇关于这个主题的博客文章:Implementing Generic Double Word Compare And Swap

然而,如果您只想防止早期免费和ABA问题,则不需要此操作。危险指针更简单,不需要特定的asm代码(只要你使用C ++ 11原子值。)我在bitbucket上有一个repo,有各种无锁算法的实验性实现:{{3} (请注意所有这些实现都是用于实验的玩具,而不是用于生产的可靠且经过测试的代码。)