[C++] Каков рекомендуемый способ выравнивания памяти на C ++ 11


Answers

Ответ на вашу проблему - std :: aligned_storage . Он может использоваться на верхнем уровне и для отдельных членов класса.

Question

Я работаю над единственной реализацией одного потребительского кольцевого буфера. У меня есть два требования:

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) Используйте alignas(..) c ++ 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) Я попытался использовать стандартную aligned_alloc(..) C ++ 11 вместо posix_memalign(..) но GCC 4.8.1 на Ubuntu 12.04 не смог найти определение в stdlib.h

Все ли гарантировано, что они делают то же самое? Моя цель - выравнивание строки в кешках, поэтому любой метод, который имеет некоторые ограничения на выравнивание (например, двойное слово), не будет делать. Независимость от платформы, которая указывает на использование стандартизованного alignas(..) является вторичной целью.

Я не знаю, имеет ли alignas(..) и __attribute__((aligned(#))) некоторый предел, который может быть ниже строки кэша на машине. Я не могу воспроизвести это больше, но при печати адресов я думаю, что я не всегда получал выровненные по 64 байта адреса с alignas(..) . Наоборот, posix_memalign(..) всегда работал. Снова я не могу воспроизвести это больше, поэтому, возможно, я ошибся.

Вторая цель - выровнять поле внутри класса / структуры с линией кэша. Я делаю это для предотвращения ложного обмена. Я пробовал следующие способы:

1) Используйте alignas(..) 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);
};

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 байта после начала объекта, поэтому, если выравнивание по иерархии кэша customer_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.




Я не знаю, является ли это наилучшим способом согласования памяти, выделенной с помощью нового оператора, но это, безусловно, очень просто!

Так делается в проходе дезинфицирующего средства в GCC 6.1.0

#define ALIGNED(x) __attribute__((aligned(x)))

static char myarray[sizeof(myClass)] ALIGNED(64) ;
var = new(myarray) myClass;

Ну, в sanitizer_common / sanitizer_internal_defs.h, также написано

// Please only use the ALIGNED macro before the type.
// Using ALIGNED after the variable declaration is not portable!        

Поэтому я не знаю, почему ALIGNED здесь используется после объявления переменной. Но это другая история.