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