wyrównanie pamięci oraz wypełnienie struktur danych

Wyrównanie pamięci w C++ służy optymalizacji czasu potrzebnego do zapisu komórek pamięci rejestrach procesora.

W Wikipedii można przeczytać, że:

  • wyrównania danych (data alignment) – ułożenie danych w pamięci komputera, gdzie adres pamięci danej jest wielokrotnością rozmiaru bloku pamięci przechowującego tę daną,
  • wypełnienie struktury danych (data structure padding) – dodatkowy obszar pamięci umieszczony pomiędzy danymi służący do zapewnienia prawidłowego wyrównania danych. Padding może być dodany pomiędzy elementami struktury danych bądź na jej końcu. Zachodzi w przypadku, gdy za elementem struktury umieszczony jest element o większym rozmiarze.

Wyrównanie pamięci jest silnie związane z architekturą procesora oraz użytym kompilatorem. W dalszych rozważaniach przyjęto architekturę x86-64 oraz kompilator g++ w wersji 15.0.

Wyrównanie pamięci

Rozważmy następujące struktury danych:

/// presentation of padding feature of C/++ language

#include <cstdint>


namespace padding {
    struct OptimalA {
        bool fieldA;
        uint8_t fieldB;
        uint16_t fieldC;
        uint32_t fieldD;
        uint64_t fieldE;
    };

    struct OptimalB {
        uint64_t fieldE;
        uint32_t fieldD;
        uint16_t fieldC;
        uint8_t fieldB;
        bool fieldA;
    };

    struct Suboptimal {
        bool fieldA;
        uint32_t fieldD;
        uint16_t fieldC;
        uint64_t fieldE;
        uint8_t fieldB;
    };

}

Powyższy kod zawiera 3 struktury danych: OptimalA, OptimalB oraz Suboptimal. Pierwsze dwie struktury prezentują optymalne ułożenie danych nie wymagające wypełnienia. Trzecia struktura, Suboptimal, zawiera ułożenie danych powodujące zastosowania wypełnienia. Padding tych struktur został przedstawiony na poniższym diagramie:

wyrównanie pamięci oraz wypełnienie struktur danych
Wygenerowano przy pomocy projektu gcc-uml

Struktury OptimalA oraz Suboptimal zawierają układ pól umieszczony w kolejności koniecznej do wprowadzenia wyrównania (większe pole następuje po mniejszym polu). Pierwsza struktura nie zawiera wyrównania, ponieważ adresy kolejnych pól są zgodne z wyrównaniem danych (data alignment). Druga struktura prezentuje przypadek wymagający zastosowanie wypełnienia. Warto zauważyć, że w tym przypadku rozmiar struktury niezoptymalizowanej jest dwa razy większy od struktury zoptymalizowanej.

Packing

Wypełnienie służy optymalizacji czasu procesora. Czasem padding jest niepożądany, np. w sytuacji, gdy:

  • rozmiar struktur danych ma większe znaczenie,
  • struktura danych służy do komunikacji.

Aby wyłączyć mechanizm wyrównania danych można posłużyć się instrukcją #pragma pack. Poniższy przykład prezentuje takie użycie.

/// presentation of padding feature of C/++ language

#include <cstdint>


namespace pragmapack {

#pragma pack(push, 1)

    struct SuboptimalPack1 {
        bool fieldA;
        uint32_t fieldD;
        uint16_t fieldC;
        uint64_t fieldE;
        uint8_t fieldB;
    };

#pragma pack(pop)
#pragma pack(push, 2)

    struct SuboptimalPack2 {
        bool fieldA;
        uint32_t fieldD;
        uint16_t fieldC;
        uint64_t fieldE;
        uint8_t fieldB;
    };

#pragma pack(pop)

}

W przypadku pierwszej klasa, SuboptimalPack1, wyrównanie jest wyłączone, natomiast dla klasy SuboptimalPack2 wyrównanie jest ograniczone do wielokrotności 2. Struktura klas przedstawiona jest na poniższym diagramie.

wyrównanie pamięci oraz wypełnienie struktur danych
Wygenerowano przy pomocy projektu gcc-uml

Podsumowanie

Wyrównanie pamięci jest mechanizmem, który na co dzień nie zawsze jest dostrzegalny, jednak może mieć istotny wpływ na rozmiar struktur danych oraz działanie programu.

Twój zespół potrzebuje wsparcia we wdrożeniu dobrych praktyk?


Leave a Reply

Your email address will not be published. Required fields are marked *