Как выровнять элементы в массиве с помощью C++11 alignas specifier

«Знаю ли я это уже?» Контрольный опрос

Как определить массив, чтобы каждый его элемент был выровнен по 64 байтам?

  1. alignas(64) int option_1[4];
  2. int CACHE_ALIGNED option_2[4];
  3. int option_3[4] CACHE_ALIGNED;
  4. typedef CACHE_ALIGNED int aligned_t;
    aligned_t option_4[4];

Где макрос CACHE_ALIGNED определяется следующим образом:

#if defined(__GNUC__)
#define CACHE_ALIGNED __attribute__((aligned(64))) // clang and GCC
#elif defined(_MSC_VER)
#define CACHE_ALIGNED __declspec(align(64))        // MSVC
#endif

Ответ в самом низу статьи.

Вариант использования: зачем выравнивать элементы массива?

Представьте себе массив состояний потоков: enum ThreadState state_flags[kMaxCPUs]; Рабочих потоков немного, и каждый поток читает и обновляет свое состояние: state_flags[this_thread_id] = kThreadAlive; Синхронизация не требуется, так как к каждому элементу массива обращается исключительно один поток. Но в чем проблема с выравниванием здесь?

Ложное совместное использование — схема использования, снижающая производительность, когда одновременно запущенные потоки хранят свои личные данные в одной и той же строке кэша.

Википедия

Чтобы избежать ложного совместного использования между рабочими процессами, каждый элемент должен быть выровнен по размеру строки кэша ЦП: 64 байта для процессоров x86 и 128 байтов для ARM. Давайте посмотрим, как выровнять элементы.

Анализ

Каждый полный тип объекта в C++ требует своего выравнивания. Стандарт C++11 удобно предлагает оператор alignof() для запроса требования выравнивания данного типа:

/* The output on my system is 16 (long double) */
printf("Alignment of 'max_align_t' is %zd\n", alignof(max_align_t));
/* The output on my system is 4 */
printf("Alignment of 'int' is %zd\n", alignof(int));

Оба спецификатора alignas и attribute((aligned)) (или declspec(align) для MSVC) увеличивают требования к выравниванию, но могут применяться только к переменным или полям структуры. Невозможно увеличить требования к выравниванию для каждого элемента массива, поскольку требование выравнивания относится ко всему массиву:

alignas(64) int option_1[4];

Этот спецификатор alignas(64) дает следующий результат:

Есть обходной путь. Мы можем использовать массив структур и выровнять поле в структуре или самой структуре:

struct AlignedField {
  alignas(64) int field;
};
AlignedField solution_1[4];
struct alignas(64) AlignedStruct {
  int field;
};
AlignedStruct solution_2[4];

Оба дают желаемый результат:

Недостатком является то, что каждый доступ к элементам массива теперь должен быть изменен с array[i] на array[i].field.

Еще одна плохая новость заключается в том, что поддержка ограничения больше alignof(max_align_t) (16 в моей системе) определяется реализацией:

Если выравнивание объекта больше, чем max_align_t, для него требуется расширенное выравнивание. Структура, член которой имеет расширенное выравнивание, является типом с чрезмерным выравниванием. Он определяется реализацией, если поддерживаются типы с избыточным выравниванием.

Справочник ЦПП

Все современные компиляторы поддерживают типы с избыточным выравниванием. Это имеет смысл, поскольку общепринятой практикой является выравнивание данных как минимум по размеру строки кэша (64 или 128 байт).

Спецификатор alignas vs attribute((align)) vs declspec

В большинстве случаев они идентичны и делают одно и то же. Однако есть небольшая разница: alignas() появляется в синтаксисе объявления как один из спецификаторов типа, поэтому приведенные ниже объявления недействительны:

// Invalid: not a type specifier
int option_3[4] alignas(64);
// Invalid: cannot be used in typedef
typedef alignas(64) int aligned_t;

Спецификатор alignas удобен, поэтому я предпочитаю использовать его, когда это возможно.

Выводы

  1. Каждый полный тип объекта в C++ требует своего выравнивания.
  2. Оператор alignof, поскольку C++11 должен запрашивать требование выравнивания.
  3. Спецификатор alignas, так как C++11 увеличивает требования к выравниванию.
  4. Требования к выравниванию применяются только к переменным и полям структуры.

Исходный код: https://github.com/berestovskyy/applied-cpp

Ответ на вопрос «Знаю ли я это уже?» Контрольный опрос

Ни один из ответов не является правильным. См. раздел «Анализ» для пояснений.