Wyrównanie pamięci (memory alignment) to umieszczanie danych w pamięci pod adresami, które są wielokrotnościami określonej liczby bajtów, odpowiadającej architekturze lub typowi danych. Prawidłowe wyrównanie jest krytyczne dla wydajności, poprawnego działania z hardwarem i niektórych instrukcji procesora.
Historia zagadnienia.
W wczesnych komputerach naruszenia wyrównania prowadziły do awarii sprzętowych (błąd magistrali), a moc obliczeniowa procesorów była wrażliwa na adresy danych, które nie były wielokrotnościami. Nawet w nowoczesnych architekturach prawidłowe wyrównanie zapewnia dostęp do danych w 1 cyklu bez penalizacji.
Problem.
Jeśli struktura jest umieszczona bez uwzględnienia wyrównania, operacje odczytu/zapisu mogą być wolniejsze lub niemożliwe (np. awaria na ARM lub MIPS). Dodatkowo narusza to działanie z niskopoziomowymi API, urządzeniami lub serializacją danych do przesyłania przez sieć.
Rozwiązanie.
W C++ do kontroli wyrównania używa się słowa kluczowego alignas (C++11) i std::align, a także nietypowych atrybutów kompilatora (__attribute__((aligned(N))) w GCC/Clang, __declspec(align(N)) w MSVC).
Przykład kodu:
struct alignas(16) MyStruct { int a; double b; char c; }; #include <iostream> #include <type_traits> int main() { std::cout << alignof(MyStruct) << std::endl; // 16 std::cout << sizeof(MyStruct) << std::endl; }
Kluczowe cechy:
Czy kolejność deklaracji członów struktury wpływa na rozmiar i wyrównanie struktury?
Tak, kolejność członów bezpośrednio określa „padding” między nimi, co może dodać dodatkowe bajty do rozmiaru struktury.
struct S1 { char a; int b; }; // zazwyczaj sizeof==8 (przy 4-bajtowym wyrównaniu) struct S2 { int b; char a; }; // zazwyczaj sizeof==8, ale czasami mniej paddingu
Czy można zmniejszyć rozmiar struktury bez naruszania wyrównania?
Tak, jeśli grupować duże człony wcześniej, a małe później. Efektywnie wyrównywać dane biorąc pod uwagę ich typ:
struct S { double d; int i; char c; }; // lepiej, niż losowo
Co się stanie, jeśli ręcznie pracować z niewyrównanymi adresami przez wskaźniki?
Standard C++ definiuje takie zachowanie jako niezdefiniowane (undefined behavior), niektóre CPU mogą generować SIGBUS lub podobny błąd.
Programista zaimplementował własną strukturę do pracy z SIMD, nie wskazując wyrównania. W rezultacie program awaryjnie się zamyka na niektórych urządzeniach ARM podczas próby operacji na niewyrównanych danych.
Zalety:
Wady:
W projekcie pracującym z danymi wideo (SSE/AVX) zastosowano alignas dla struktur bufora, co pozwoliło na efektywne wykorzystanie instrukcji SIMD.
Zalety:
Wady: