Двойное выравнивание

После обсуждения сэта почтаЯ понял, что основной причиной выравнивания элементов структуры является производительность (и некоторые архитектурные ограничения).

Если мы будем исследовать Microsoft (Visual C ++), Borland / CodeGear (C ++ - Builder), Digital Mars (DMC) и GNU (GCC) при компиляции для 32-битной x86: выравнивание дляint 4 байта и еслиint не выровнен, может случиться так, что будут прочитаны 2 ряда банков памяти.

Мой вопрос, почему бы не сделатьdouble быть выровненными также 4 байта? 4 байта выровненыdouble также вызовет 2 строки чтения банков памяти ....

Например, в следующем примере, так какdouble выровнен по 8, фактический размер структуры будетsizeof(char) + (alignment for double padding) + sizeof(int) = 20 bytes.

typedef struct structc_tag{
    char        c;
    double      d;
    int         s;
} structc_t;

Спасибо

Ответы на вопрос(4)

Расширенный комментарий:

Согласно документации GCC о-malign-double:

Aligning double variables on a two-word boundary produces code that runs somewhat faster on a Pentium at the expense of more memory.

On x86-64, -malign-double is enabled by default.

Warning: if you use the -malign-double switch, structures containing the above types are aligned differently than the published application binary interface specifications for the 386 and are not binary compatible with structures in code compiled without that switch.

Слово здесь означает слово i386, которое составляет 32 бита.

Windows использует 64-битное выравниваниеdouble значения даже в 32-битном режиме, в то время как Unys, совместимые с SysV i386 ABI, используют 32-битное выравнивание. 32-битный Windows API, Win32, происходит от Windows NT 3.1, которая, в отличие от версий Windows нынешнего поколения, ориентирована на Intel i386, Alpha, MIPS и даже на малоизвестный Intel i860. Поскольку родные системы RISC, такие как Alpha и MIPS, требуютdouble Для выравнивания 64-битных значений (в противном случае происходит сбой оборудования), переносимость могла быть обоснованием 64-битного выравнивания в Win32 i386 ABI.

64-разрядные системы x86, также известные как AMD64, x86-64 или x64, требуютdouble значения должны быть выровнены на 64 бита, в противном случае возникает ошибка смещения, и аппаратное обеспечение выполняет дорогостоящее исправление. что значительно замедляет доступ к памяти. Вот почемуdouble все 64-битные значения выровнены во всех современных x86-64 ABI (SysV и Win32).

Прежде всего, это архитектура, которая налагает требование выравнивания, некоторые будут допускать обращения к памяти без выравнивания, другие - нет.

Давайте приниматьx86-32bit Платформа Windows в качестве примера, в этой платформе требование выравнивания дляint а такжеlong является4 bytes а также8 bytes соответственно.

Понятно почемуint Требование выравнивания4 bytesпросто, чтобы процессор мог прочитать все это только одним доступом.

Причина, почему требование выравнивания дляdoulbe является8 bytes и не4 bytes, потому что если бы это было4 bytes затем подумайте о том, что произойдет, если этот двойник был расположен по адресу60 и размер строки кэша был64bitsв этом случае процессору нужно загрузить 2 строки кэша из памяти в кэш, но если этоdouble было выровнено, этого не произойдет, так как в этом случаеdouble всегда будет частью одной строки кэша и не будет разделена между двумя.

   ...58 59|60 61 62 63    64 65 66 67|68 69 70 71...
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
 ----------+  +  +  +  .  .  +  +  +  +--------------
           |           .  .           |
 ----------+  +  +  +  .  .  +  +  +  +-------------- 
                       .  .         
      Cache Line 1     .  .  Cache Line 2
     -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -

Этот вопрос сильно зависит от платформы с точки зрения архитектуры процессора. Например, в архитектурах, которые предусматривают штраф за работу с выровненными по 4 байта адресами, сопоставление переменных (влияющих на их адреса) с 4 байтами может избежать такого штрафа.

Компиляторы довольно хороши с такими вещами, особенно когда вы предоставляете им целевую архитектуру ЦП, для которой нужно оптимизировать, чтобы они могли сделать большую часть этого для вас, а также множество других оптимизаций. Посмотрите на GCC-march например, флаг, который позволяет вам ориентироваться на архитектуры ЦП.

Большинство компиляторов автоматически выравнивают значения данных по размеру слова платформы или по размеру типа данных, в зависимости от того, что меньше. Подавляющее большинство потребительских и корпоративных процессоров используют 32-битный размер слова. (Даже 64-битные системы обычно используют 32-битный формат как собственный размер слова)

Таким образом, упорядочивание членов в вашей структуре может привести к потере памяти. В вашем конкретном случае с вами все в порядке. Я добавлю в комментарии фактический объем используемой памяти:

typedef struct structc_tag{
          char        c; // 1 byte
                         // 3 bytes (padding)
          double      d; // 8 bytes
          int         s; // 4 bytes
} structc_t;             // total: 16 bytes

Это правило применимо и к структурам, поэтому даже если вы переставите их так, чтобы наименьшее поле было последним, у вас все равно будет структура того же размера (16 байт).

typedef struct structc_tag{
          double      d; // 8 bytes
          int         s; // 4 bytes
          char        c; // 1 byte
                         // 3 bytes (padding)
} structc_t;             // total: 16 bytes

Если бы вы объявили больше полей размером менее 4 байт, вы могли бы увидеть некоторые сокращения памяти, если бы вы сгруппировали их по размеру. Например:

typedef struct structc_tag{
          double      d1; // 8 bytes
          double      d2; // 8 bytes
          double      d3; // 8 bytes
          int         s1; // 4 bytes
          int         s2; // 4 bytes
          int         s3; // 4 bytes
          short       s4; // 2 bytes
          short       s5; // 2 bytes
          short       s6; // 2 bytes
          char        c1; // 1 byte
          char        c2; // 1 byte
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 48 bytes

Объявление глупой структуры может тратить много памяти, если только компилятор не переупорядочит ваши элементы (что, в общем случае, не будет, без явного указания)

typedef struct structc_tag{
          int         s1; // 4 bytes
          char        c1; // 1 byte
                          // 3 bytes (padding)
          int         s2; // 4 bytes
          char        c2; // 1 byte
                          // 3 bytes (padding)
          int         s3; // 4 bytes
          char        c3; // 1 byte
                          // 3 bytes (padding)
} structc_t;              // total: 24 bytes 
                          // (9 bytes wasted, or 38%)
                          // (optimal size: 16 bytes (1 byte wasted))

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

Производительность памяти определяется словами: загрузка из памяти происходит поэтапно, в зависимости от размещения данных. Если данные охватывают одно слово (т. Е. Выровнено по слову), необходимо загрузить только это слово. Если он не выровнен правильно (то есть int в 0x2), процессор должен загрузить 2 слова, чтобы правильно прочитать его значение. То же самое относится к двойным числам, которые обычно занимают 2 слова, но если смещены, занимают 3. В 64-битных системах, где возможна собственная загрузка 64-битных величин, они ведут себя как 32-битные целые числа в 32-битных системах в том случае, если они правильно выровнены , они могут быть получены с одной загрузкой, но в противном случае они потребуют 2.

ВАШ ОТВЕТ НА ВОПРОС