Я работаю над единственной реализацией одного кольцевого буфера для одного производителя. У меня есть два требования:
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.
};
Позвольте мне сначала заняться точкой 1, т.е. выровнять один класс, выделенный оболочкой класса. Есть несколько способов:
1) Используйте спецификатор С++ 11 alignas(..)
:
template<typename T, uint64_t num_events>
class alignas(CACHE_LINE_SIZE) RingBuffer {
public:
....
private:
// All the private fields.
};
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>();
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)));
4) Я попытался использовать стандартную С++ 11 функцию aligned_alloc(..)
вместо posix_memalign(..)
, но GCC 4.8.1 на Ubuntu 12.04 не смог найти определение в stdlib.h
Все ли они гарантированно выполняют одно и то же? Моя цель - выравнивание строки в кешках, поэтому любой метод, который имеет некоторые ограничения на выравнивание (например, двойное слово), не будет делать. Независимость от платформы, которая указывает на использование стандартизованного alignas(..)
, является вторичной целью.
Неясно, есть ли у alignas(..)
и __attribute__((aligned(#)))
некоторый предел, который может быть ниже строки кэша на машине. Я не могу воспроизвести это больше, но при печати адресов я думаю, что я не всегда получал согласованные адреса по 24 байта с помощью alignas(..)
. Наоборот, posix_memalign(..)
, казалось, всегда работал. Снова я не могу воспроизвести это больше, поэтому, возможно, я ошибся.
Вторая цель - выровнять поле внутри класса/структуры в строке кэша. Я делаю это для предотвращения ложного обмена. Я пробовал следующие способы:
1) Используйте спецификатор С++ 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);
};
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)));
};
Оба эти метода, похоже, выравнивают consumer_sequence
до адреса 64 байта после начала объекта, поэтому, если consumer_sequence
выравнивается по кешу, зависит от того, был ли сам объект привязан к кешу. Здесь мой вопрос: есть ли лучшие способы сделать то же самое?
EDIT: Причина, по которой aligned_alloc не работала на моей машине, заключалась в том, что я был на eglibc 2.15 (Ubuntu 12.04). Он работал над более поздней версией eglibc.
На странице man: The function aligned_alloc() was added to glibc in version 2.16
.
Это делает его довольно бесполезным для меня, так как я не могу требовать такую последнюю версию eglibc/glibc.