Herlihy和Shavit的书(The Art of Multiprocessor Programming)对内存回收的解决方案使用了Java的AtomicStampedReference<T>;
。
要用C ++为x86_64写一个,我想要至少需要一个12字节的交换操作 - 对于64位指针为8,对于int为4。
是否有x86硬件支持,如果没有,任何关于如何在没有它的情况下进行无等待内存回收的指针?
答案 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;
};
以无锁方式:
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} (请注意所有这些实现都是用于实验的玩具,而不是用于生产的可靠且经过测试的代码。)