Optymalizacja rozmieszczenia danych w pamięci to kluczowa cecha Rust, która pozwala oszczędzać zasoby bez utraty bezpieczeństwa kodu. Enum Layout to sposób, w jaki kompilator rozmieszcza warianty enum (z wariantami-strukturami lub prymitywami), a także same pola dowolnej struktury. W innych językach taka optymalizacja często jest ukryta, podczas gdy w Rust bardzo ważne jest uwzględnienie kolejności pól, aby uniknąć nadmiernego zużycia pamięci.
Jeśli kolejność pól w strukturze jest źle dobrana, struktura zacznie "rosnąć" ze względu na cechy wyrównania danych. W przypadku enum z danymi skojarzonymi sytuacja staje się bardziej skomplikowana — rozmiar enum określany jest przez największy wariant plus rozmiar dyskryminatora. Ignorowanie tego prowadzi do nadmiernego konsumowania pamięci oraz obniżenia wydajności pamięci podręcznej procesora.
Aby skutecznie pakować struktury i enum, warto umieszczać najpierw najbardziej "szerokie" pola, a następnie węższe, biorąc pod uwagę poduszki, które kompilator może dodać. Dla enum — wybierać strukturę wariantów tak, aby dążyły do maksymalnego rozmiaru tylko wtedy, gdy jest to uzasadnione.
Przykład kodu:
struct BadAlign { a: u8, b: u32, c: u16, } struct GoodAlign { b: u32, c: u16, a: u8, } enum Packet { A(u8), B(u32, [u8; 10]), }
Kluczowe cechy:
Czy można zmniejszyć rozmiar struktury, po prostu zmieniając kolejność pól?
Tak. Jeśli pola są uporządkowane w kolejności malejącej rozmiaru, kompilator często zmniejsza liczbę poduszek, tym samym redukując ogólny rozmiar struktury.
println!("{}", std::mem::size_of::<BadAlign>()); // na przykład 12 println!("{}", std::mem::size_of::<GoodAlign>()); // na przykład 8
Czy jakakolwiek kolejność pól wpływa na wydajność dostępu do nich?
Kolejność sama w sobie nie wpływa na szybkość dostępu przez pola. Jednak przy sekwencyjnym iterowaniu przez strukturę na niskim poziomie (np. z instrukcjami SIMD lub przy pracy z tablicami struktur w pętli) prawidłowe wyrównanie przyspiesza dostęp dzięki lepszemu wykorzystaniu pamięci podręcznej.
Czy jeśli jeden wariant enum jest bardzo duży, każdy egzemplarz enum zajmuje tyle samo pamięci, nawet jeśli jest to inny wariant?
Tak, rozmiar enum jest zawsze określany przez największy z wariantów plus dyskryminator. Każdy Packet zajmuje rozmiar B, nawet jeśli zawiera A.
** Negatywny przypadek
W strukturze pola u8, następnie u64. Użycie w tablicy z 100000 rekordów zużywa do gigabajta pamięci z powodu poduszek.
Zalety:
Wady:
** Pozytywny przypadek
Struktury uporządkowane według szerokości pól, a duże warianty enum zostały przeniesione do Box, podczas gdy małe pozostały w miejscu.
Zalety:
Wady: