Структура C (++) принудительно добавляет дополнительное дополнение

Я видел бесчисленное количество вопросов в форме «Мне не нравится дополнение, как его отключить», но еще не нашел ничего о том, чтобы заставить компилятор предоставить дополнительное дополнение.

Конкретный случай, который у меня есть, выглядит как

struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
};

Где vect2 — это просто struct {double x; double y;} vect2. Чтобы использовать SSE2, мне нужно иметь возможность загрузить пару двойников, выровненных по 16-байтовым границам. Раньше это работало, пока я не добавил дополнительные int, увеличив размер моей структуры с 48 до 56 байт. Результат - сегфоулты.

Есть ли какая-то директива компилятора, которую я могу использовать, которая либо говорит: «дополнить эту структуру, чтобы сделать ее длиной кратной 16 байтам», либо «эта структура имеет выравнивание 16 байтов»? Я знаю, что мог бы сделать это вручную (например, добавив дополнительный символ [12]), но я бы предпочел просто сказать компилятору (GCC, предпочтительно совместимый с ICC), и мне не нужно делать это вручную, если я изменю структура в будущем.


person zebediah49    schedule 22.06.2012    source источник
comment
В C++11 для этой цели теперь есть alignas.   -  person ildjarn    schedule 22.06.2012
comment
Я не думаю, что GCC еще не реализовал это.   -  person chris    schedule 22.06.2012
comment
Затем см. stackoverflow.com/questions/6959261/how-can -i-simulate-alignast   -  person Ben Voigt    schedule 22.06.2012
comment
Не могли бы вы соединить vect2 с __m128? Это должно указать gcc выровнять вашу структуру по 16 байтам в стеке.   -  person ecatmur    schedule 22.06.2012
comment
На самом деле я использовал его как объединение с __m128d, хотя я удалил его по причинам, по которым мне сказали, что это ужасная идея с точки зрения результирующего сгенерированного кода и что вместо него следует использовать _mm_load_pd(). Однако простое использование этого для выравнивания может сработать.   -  person zebediah49    schedule 22.06.2012
comment
Другой вариант принудительного заполнения — добавить в структуру безымянные битовые поля. struct particle { vect2 s; ...; double mass; unsigned long : 32; unsigned long : 32; int boxNum; }; (в C вы можете переносимо использовать одно битовое поле unsigned long long : 64; для заполнения, не уверен, есть ли в C++ переносимый целочисленный тип с ›= 64 битами).   -  person Daniel Fischer    schedule 22.06.2012
comment
@Daniel: С++ 11 теперь официально имеет long long, но большинство компиляторов все равно поддерживали его как расширение С++ 03.   -  person ildjarn    schedule 23.06.2012


Ответы (5)


Вы можете вложить две структуры, чтобы заполнить их автоматически, без необходимости самостоятельно отслеживать размер.

struct particle
{
    // ...
};

{
    particle p;
    char padding[16-(sizeof(particle)%16)];
};

Эта версия, к сожалению, добавляет 16 байт, если структура уже кратна 16. Это неизбежно, потому что стандарт не разрешает массивы нулевой длины.

Некоторые компиляторы допускают массивы нулевой длины в качестве расширения, и в этом случае вы можете сделать это вместо этого:

struct particle_wrapper
{
    particle p;
    char padding[sizeof(particle)%16 ? 16-(sizeof(particle)%16) : 0];
};

Эта версия не добавляет никаких байтов заполнения, если структура уже кратна 16.

person Mark Ransom    schedule 22.06.2012
comment
Вы можете избежать добавления 16 байт, выполнив char padding[ (sizeof(particle) + 15) & ~15 ]. - person Pedro; 23.06.2012
comment
Это так, но работает только потому, что 16 — это степень двойки. Это легче понять, если учесть, что ~15 равно 1..10000 в двоичном формате, т. е. отфильтровывает последние четыре бита, оставляя число, кратное 16. Так как это просто усекает число до кратно 16, мы должны сначала добавить 15, чтобы получить следующее по величине кратное. - person Pedro; 24.06.2012
comment
@Pedro, подумайте, если sizeof(particle) == 1 - ваше заполнение будет 16 байтов, что приведет к общему размеру 17 байтов. Я думаю, вы имели в виду 15 вместо ~15, но даже это неправильно, потому что это добавило бы только 1 байт вместо 15. %16 и &15 - это идентичные операции. Вам нужно добавить инверсию размера структуры, что я и сделал с 16-.... - person Mark Ransom; 24.06.2012
comment
Извините, я забыл вычесть фактический размер struct. Правильное объявление char padding[ ((sizeof(particle) + 15) & ~15) - sizeof(particle)]. В любом случае, все это спорно, поскольку в gcc добавление __attribute__((aligned(16))) в конце объявления структуры делает это автоматически. - person Pedro; 24.06.2012
comment
@ Педро, с этой поправкой мы с тобой наконец-то согласны. Единственная проблема теперь, когда размер уже кратен 16, и вы пытаетесь создать padding[0], даже если компилятор это позволяет, он может зарезервировать лишний байт. - person Mark Ransom; 24.06.2012
comment
К сожалению, padding[0] незаконен; массивы должны иметь хотя бы один элемент. - person ecatmur; 25.06.2012
comment
@ecatmur: заполнение нулевого размера законно в gcc и должно имеют размер нулевой байт. Опять же, все это массовый взлом, которого можно избежать, используя __attribute__((aligned(16))). - person Pedro; 25.06.2012

В gcc вы можете выравнивать произвольные типы и переменные с __attribute__((aligned(...))). Для вашего примера это будет

struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
} __attribute__((aligned (16)));

Это автоматически дополняет структуру так, чтобы ее массивы были правильно выровнены.

person Pedro    schedule 23.06.2012

Я добавляю к этому свой собственный ответ, если кто-то ищет решение. Решение Марка является изящным и удовлетворяет автоматическим требованиям, но это не так, когда я остановился на нем. Я хотел этого избежать, поэтому и задал вопрос, но есть "тривиальное" решение:

struct particle{
  vect2 s;
  vect2 v;
  int rX;
  int rY;
  double mass;
  int boxNum;
  char padding[12];
};

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

Обратите внимание, что мой struct был 56 байт, и я добавил 12, чтобы получить 64. Эта математика не работает, потому что конечный int уже был дополнен 4 байтами до 8-байтовой границы; struct раньше было всего 52 байта. Добавление только 5 char сработало бы, сделав struct длиной 57 байт, которые были бы дополнены до 64, но это не такое хорошее решение, поэтому я использовал 12, чтобы все работало точно.

person zebediah49    schedule 23.06.2012
comment
Это кажется разумным, учитывая требования к производительности; ради ваших коллег и будущих сопровождающих (включая вас самих) пожалуйста, прокомментируйте исправление и добавьте время компиляции, утверждающее, что размер struct кратен 16 байтам. - person ecatmur; 25.06.2012
comment
Есть ли какая-то конкретная причина, по которой вы не хотели оставлять это компилятору, например. с __attribute__((aligned(16)))? - person Pedro; 27.06.2012

Не проверено, но это может сработать:

#include <xmmintrin.h>

struct particle{
  union {
    vect2 s;
    __m128 s_for_alignment;
  };
  union {
    vect2 v;
    __m128 v_for_alignment;
  };
  ...
};

Я знаю, что раньше у gcc были проблемы с правильным выравниванием __m128, но сейчас они должны быть исправлены.

person ecatmur    schedule 22.06.2012
comment
В этом случае я мог бы также пойти с union vect2 { __m128d s; struct{ double x; double y;};};, но да, это может быть выходом. - person zebediah49; 22.06.2012
comment
Тестирование показывает, что это работает примерно на 10% медленнее, чем заполнение вручную; Я не совсем уверен, почему. - person zebediah49; 22.06.2012
comment
Сумасшедший. Есть ли отличия в сгенерированном asm? - person ecatmur; 22.06.2012
comment
Чтение ASM не является моей сильной стороной, но когда я включаю объединение (также дополняю структуру, чтобы я сравнивал только одно изменение, количество инструкций, записанных Callgrind, увеличилось с 11G до 13G. Сравнивая один из медленных разделов, Я заметил, что в цикле 20 вместо 10 инструкций: он идет от movsd{3}, addsd{2}, movsd{2}, ucomisd, jbe к mov, movsd, mov{8}, addsd, mov{3}, movsd, addsd, movsd, ucomisd, movsdjbe Большинство движений, кажется, перетасовывают вещи с %rdx, %rax и 0x??(%rsp). - person zebediah49; 22.06.2012

В новой спецификации C++11 также есть новая функция для этого, хотя я не верьте, что многие поставщики уже внедрили их.

Вы можете попробовать прагму pack, хотя она не поддерживается спецификацией. Однако и GCC, и MS поддерживают его.

Это выравнивает структуру по границам в 1 байт, хотя вы можете изменить число на любое другое.

#pragma pack(push,1)
// ...
#pragma pack(pop)

Обновить:

Так что, по-видимому, вышеизложенное не будет работать, поскольку оно только сжимает отступы, а не расширяет их. К сожалению, сегодня днем ​​у меня нет тестовой среды.

Возможно, использование анонимного союза сработает. Я знаю, что он расширится до самого большого размера, хотя я не знаю, получите ли вы какие-либо гарантии относительно выравнивания в противном случае.

template<typename T, size_t padding_size>
  struct padded_field {
    union {
      T value;
      uint8_t padding[padding_size];
    };
  };
person Tom Kerr    schedule 22.06.2012
comment
Я пробовал это; когда установлено значение 4, оно сжимается до 52 байтов... но когда установлено значение 16, оно остается на уровне 56, поэтому я предположил, что это не расширяет заполнение, а только обеспечивает более плотную упаковку. - person zebediah49; 22.06.2012
comment
Я не знаком с реализацией gcc, но согласно документации Visual C++, Выравнивание элемента будет осуществляться по границе, кратной либо n, либо кратному размеру элемента, в зависимости от того, что меньше. - person James McNellis; 22.06.2012