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

Что такое выравнивание данных?

Выравнивание данных — это размещение данных в памяти таким образом, чтобы ЦП мог более эффективно обращаться к ним. Например, 32-разрядный ЦП может более эффективно обращаться к данным, если данные организованы в виде 4-байтовых слов, расположенных по адресу, который делится без остатка на размер данных.

Структуры выделяются как непрерывный блок памяти, где каждый элемент имеет собственный размер, смещение и выравнивание. Давайте создадим структуру Article и посмотрим, как компилятор определяет размещение ее элементов. Предполагается 64-битный процессор.

struct Article
{
   char *title;
   char *author;
   char *body;
   bool is_published;
   bool is_member_only;
   int no_comments;
   int no_claps;
};

Структуры выравниваются в соответствии с естественными требованиями выравнивания самого большого элемента. Это выравнивание достигается путем разделения выделенной памяти на сегменты одинакового размера. Размер каждого сегмента соответствует максимальному выравниванию.

Структура Article выровнена по 8 байтам, что соответствует выравниванию типа указателя char с максимальным выравниванием. Для правильного выравнивания каждый элемент в структуре должен быть размещен со смещением, соответствующим его естественному выравниванию. Для этого может потребоваться вставка байтов заполнения между элементами структуры.

Обратите внимание, что перед no_comments есть 2 байта заполнения. Это связано с тем, что no_comments выравнивается по 4 байтам и сразу следует за is_published и is_member_only, которые занимают 2 байта пространства, поэтому было добавлено еще 2 байта, чтобы обеспечить естественное выравнивание no_comments. Также обратите внимание, что 4 байта заполнения добавляются в конец после члена no_claps, чтобы сформировать полный 8-байтовый сегмент.

Отсутствие отступов для уменьшения размера

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

struct Article
{
   char *title;
   char *author;
   char *body;
   bool is_published;
   bool is_member_only;
   int no_comments;
   int no_claps;
}__attribute__((packed));

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

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

/*
   gcc X84-64 assembly code for calling the function 
   "Article_Copy" on both regular and packed structs.
   Assembly code generated using godbolt.org
*/

// Regular struct
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 48
        mov     QWORD PTR [rbp-48], 
        mov     QWORD PTR [rbp-40], 0
        mov     QWORD PTR [rbp-32], 0
        mov     QWORD PTR [rbp-24], 0
        mov     QWORD PTR [rbp-16], 0
        push    QWORD PTR [rbp-16]
        push    QWORD PTR [rbp-24]
        push    QWORD PTR [rbp-32]
        push    QWORD PTR [rbp-40]
        push    QWORD PTR [rbp-48]
        call    Article_Copy
        add     rsp, 40
        mov     eax, 0
        leave
        ret

// Packed struct
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 56
        mov     QWORD PTR [rbp-64], 0
        mov     QWORD PTR [rbp-56], 0
        mov     QWORD PTR [rbp-48], 0
        mov     QWORD PTR [rbp-40], 0
        mov     WORD PTR [rbp-32], 0
        sub     rsp, 40
        mov     rax, rsp
        mov     rcx, QWORD PTR [rbp-64]
        mov     rbx, QWORD PTR [rbp-56]
        mov     QWORD PTR [rax], rcx
        mov     QWORD PTR [rax+8], rbx
        mov     rcx, QWORD PTR [rbp-48]
        mov     rbx, QWORD PTR [rbp-40]
        mov     QWORD PTR [rax+16], rcx
        mov     QWORD PTR [rax+24], rbx
        movzx   edx, WORD PTR [rbp-32]
        mov     WORD PTR [rax+32], dx
        call    Article_Copy
        add     rsp, 40
        mov     eax, 0
        mov     rbx, QWORD PTR [rbp-8]
        leave
        ret

Переопределение выравнивания по умолчанию

Как упоминалось ранее, структуры выравниваются в соответствии с естественными требованиями выравнивания самого большого члена. Но что, если вы хотите применить определенное выравнивание к своей структуре? Для этого используйте выровненный атрибут вместе с упакованным атрибутом.

struct Article
{
   char *title;
   char *author;
   char *body;
   bool is_published;
   bool is_member_only;
   int no_comments;
   int no_claps;
}__attribute__((packed,aligned(4));

Структура Article теперь выровнена по 4 байтам вместо 8-байтового выравнивания типа указателя char. Обратите внимание, что компилятор может по-прежнему вставлять байты заполнения для обеспечения выравнивания данных.

Последнее примечание

Выравнивание структуры выполняется автоматически компилятором, поскольку ЦП намного быстрее получает доступ к выровненным данным. Выравнивание структуры достигается путем разделения пространства памяти структуры на сегменты одинакового размера. Размер каждого сегмента равен размеру наибольшего выравнивания. Для этого может потребоваться вставка байтов заполнения между элементами для обеспечения правильного выравнивания данных. Вы также можете указать пользовательское выравнивание, используя атрибутalign. В средах с ограничениями, где меньшие размеры структур предпочтительнее скорости, можно использовать упакованный атрибут, чтобы опустить заполнение и уменьшить размер структуры, но выравнивание данных не гарантируется.

Это все на данный момент.

Ваша поддержка и поддержка очень много значат для меня, поэтому, пожалуйста, подпишитесь на меня или купите мне кофе на Ko-fi.com, это так ценно!

Спасибо за чтение, и увидимся в следующем.

Повышение уровня кодирования

Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:

  • 👏 Хлопайте за историю и подписывайтесь на автора 👉
  • 📰 Смотрите больше контента в публикации Level Up Coding
  • 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"

🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу