Raj*_*jiv 66 c++ memory-alignment c++11
我正在研究单个生产者单个消费者环缓冲区实现.我有两个要求:
1)将单个堆分配的环形缓冲区实例与高速缓存行对齐.
2)将环形缓冲区内的字段与高速缓存行对齐(以防止错误共享).
我的班级看起来像:
#define CACHE_LINE_SIZE 64 // To be used later.
template<typename T, uint64_t num_events>
class RingBuffer { // This needs to be aligned to a cache line.
public:
....
private:
std::atomic<int64_t> publisher_sequence_ ;
int64_t cached_consumer_sequence_;
T* events_;
std::atomic<int64_t> consumer_sequence_; // This needs to be aligned to a cache line.
};
Run Code Online (Sandbox Code Playgroud)
让我首先解决第1点,即对齐单个堆分配的类实例.有几种方法:
1)使用c ++ 11 alignas(..)
说明符:
template<typename T, uint64_t num_events>
class alignas(CACHE_LINE_SIZE) RingBuffer {
public:
....
private:
// All the private fields.
};
Run Code Online (Sandbox Code Playgroud)
2)使用posix_memalign(..)
+放置new(..)
而不改变类定义.这不受平台独立的影响:
void* buffer;
if (posix_memalign(&buffer, 64, sizeof(processor::RingBuffer<int, kRingBufferSize>)) != 0) {
perror("posix_memalign did not work!");
abort();
}
// Use placement new on a cache aligned buffer.
auto ring_buffer = new(buffer) processor::RingBuffer<int, kRingBufferSize>();
Run Code Online (Sandbox Code Playgroud)
3)使用GCC/Clang扩展 __attribute__ ((aligned(#)))
template<typename T, uint64_t num_events>
class RingBuffer {
public:
....
private:
// All the private fields.
} __attribute__ ((aligned(CACHE_LINE_SIZE)));
Run Code Online (Sandbox Code Playgroud)
4)我尝试使用C++ 11标准化aligned_alloc(..)
函数代替posix_memalign(..)
但是Ubuntu 12.04上的GCC 4.8.1无法找到stdlib.h
所有这些都保证做同样的事情吗?我的目标是缓存行对齐,所以任何对齐都有限制的方法(比如双字)都行不通.指向使用标准化的平台独立性alignas(..)
是次要目标.
我不是是否清晰alignas(..)
,并__attribute__((aligned(#)))
有一定的限额,这可能是本机上的高速缓存线以下.我不能再重现这一点,但在打印地址时,我认为我并不总是得到64字节对齐的地址alignas(..)
.相反,posix_memalign(..)
似乎总是有效.我再也无法重现这一点,所以也许我犯了一个错误.
第二个目标是将类/结构中的字段与高速缓存行对齐.我这样做是为了防止误共享.我尝试了以下方法:
1)使用C++ 11 alignas(..)
说明符:
template<typename T, uint64_t num_events>
class RingBuffer { // This needs to be aligned to a cache line.
public:
...
private:
std::atomic<int64_t> publisher_sequence_ ;
int64_t cached_consumer_sequence_;
T* events_;
std::atomic<int64_t> consumer_sequence_ alignas(CACHE_LINE_SIZE);
};
Run Code Online (Sandbox Code Playgroud)
2)使用GCC/Clang扩展 __attribute__ ((aligned(#)))
template<typename T, uint64_t num_events>
class RingBuffer { // This needs to be aligned to a cache line.
public:
...
private:
std::atomic<int64_t> publisher_sequence_ ;
int64_t cached_consumer_sequence_;
T* events_;
std::atomic<int64_t> consumer_sequence_ __attribute__ ((aligned (CACHE_LINE_SIZE)));
};
Run Code Online (Sandbox Code Playgroud)
这两种方法似乎都consumer_sequence
在对象开始后对应一个64字节的地址,因此consumer_sequence
缓存是否对齐取决于对象本身是否与缓存对齐.我的问题是 - 有没有更好的方法来做同样的事情?
编辑: 对齐我没有在我的机器上工作的原因是我在eglibc 2.15(Ubuntu 12.04).它适用于eglibc的更高版本.
从手册页:The function aligned_alloc() was added to glibc in version 2.16
.
这对我来说非常无用,因为我不能要求这样的最新版本的eglibc/glibc.
Gle*_*aum 30
不幸的是,我发现最好的是分配额外的空间,然后使用"对齐"部分.因此,RingBuffer new
可以请求额外的64个字节,然后返回其中第一个64字节对齐的部分.它浪费了空间,但会给你所需的对齐.您可能需要在返回到实际的alloc地址之前设置内存以取消分配它.
[Memory returned][ptr to start of memory][aligned memory][extra memory]
Run Code Online (Sandbox Code Playgroud)
(假设没有来自RingBuffer的继承)类似于:
void * RingBuffer::operator new(size_t request)
{
static const size_t ptr_alloc = sizeof(void *);
static const size_t align_size = 64;
static const size_t request_size = sizeof(RingBuffer)+align_size;
static const size_t needed = ptr_alloc+request_size;
void * alloc = ::operator new(needed);
void *ptr = std::align(align_size, sizeof(RingBuffer),
alloc+ptr_alloc, request_size);
((void **)ptr)[-1] = alloc; // save for delete calls to use
return ptr;
}
void RingBuffer::operator delete(void * ptr)
{
if (ptr) // 0 is valid, but a noop, so prevent passing negative memory
{
void * alloc = ((void **)ptr)[-1];
::operator delete (alloc);
}
}
Run Code Online (Sandbox Code Playgroud)
对于具有RingBuffer
64字节对齐的数据成员的第二个要求,如果您知道this
对齐的开头,则可以填充以强制对齐数据成员.