题
赫利希和沙维特的书(多处理器编程的艺术)内存回收的解决方案使用Java的 AtomicStampedReference<T>;
.
我想,要用 C++ 为 x86_64 编写一个交换操作,至少需要 12 字节交换操作 - 8 个字节用于 64 位指针,4 个字节用于 int。
是否有 x86 硬件支持,如果没有,是否有任何关于如何在没有它的情况下进行无等待内存回收的指示?
解决方案
是的,有硬件的支持,虽然我不知道它是由C ++库曝光。无论如何,如果你不介意做一些低级别的不可移植的汇编语言挂羊头卖狗肉 - 仰望CMPXCHG16B指令英特尔手册
。其他提示
视窗给你一堆互锁功能一>是原子弹,大概可以用来做你想要什么。存在类似的功能为其它平台,我相信升压具有互锁库以及
您的问题是不是超级清楚,我没有的赫利希和沙维特周围铺设的副本。或许,如果你阐述或放弃伪代码概述你想要做什么,我们可以给你一个更具体的答案。
好吧,希望我有这本书,
对于其他可能提供答案的人来说,重点是实现此类:
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 个寄存器。
如果您查看书 pragma 9.8.1,AtomicMarkableReference 与整数标记中的单个位相同。作者建议从指针中“窃取”一点,以从单个单词中提取标记和指针(几乎被引用),这显然意味着他们想要使用单个原子寄存器来完成此操作。
然而,可能有一种方法可以在没有它的情况下消除无等待内存回收。我不知道。
是,64支持此;你需要使用CMPXCHG16B。
可以通过依赖于一个事实,即指针将使用小于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;
}
然后您就需要构建一个标记指针类型。我假设具有128字节(像的Nehalem)高速缓存行宽度为40位的指针。对准高速缓存线将通过减少假共享,竞争等给予极大的速度提升;这具有使用的很多更多存储器,在某些情况下的明显的折衷。
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);
}
};
我还没有机会调试代码,所以你需要做的,但这是一般的想法。
请注意,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
cmpxchg16b 操作提供了预期的操作,但请注意一些较旧的 x86-64 处理器没有此指令。
然后,您只需要使用计数器和指针以及 asm 内联代码构建一个实体。我在这里写了一篇关于该主题的博客文章:实现通用双字比较和交换
不过,如果您只是想防止早期释放和 ABA 问题,则不需要此操作。危险指针更简单,不需要特定的 asm 代码(只要使用 C++11 原子值。)我在 bitbucket 上有一个存储库,其中包含各种无锁算法的实验实现: 无锁实验 (请注意,所有这些实现都是实验玩具,而不是可靠且经过测试的生产代码。)