データのメモリ配置の最適化は、リソースを安全性を損なわずに節約できるRustの重要な特徴です。列挙型レイアウトは、コンパイラが列挙型のバリエーション(構造体やプリミティブを含む)および任意の構造体のフィールドを配置する方法です。他の言語ではそのような最適化はしばしば隠されますが、Rustではメモリの浪費を避けるためにフィールドの順序を考慮することが非常に重要です。
構造体のフィールドの順序が不適切な場合、データのアライメントの特性により、その構造体はサイズが「膨らむ」ことになります。関連データを持つ列挙型の場合、状況はさらに複雑になります。列挙型のサイズは最大のバリエーションのサイズとディスクリミネーターのサイズに依存します。この点を軽視すると、メモリの過剰消費やキャッシュ性能の低下を招くことがあります。
構造体や列挙型の効率的なパッキングのためには、最初に「広い」フィールドを配置し、次により狭いフィールドを配置し、コンパイラが追加する可能性のあるパディングを考慮することが重要です。列挙型については、最大サイズを追求しないようにバリエーションの構造を選ぶべきです。
コードの例:
struct BadAlign { a: u8, b: u32, c: u16, } struct GoodAlign { b: u32, c: u16, a: u8, } enum Packet { A(u8), B(u32, [u8; 10]), }
重要な特徴:
フィールドの順序を変えるだけで構造体を小さくすることはできますか?
はい。フィールドがサイズの降順で並んでいる場合、コンパイラはしばしばパディングの量を減少させ、その結果、構造体の全体のサイズを小さくします。
println!("{}", std::mem::size_of::<BadAlign>()); // 例えば、12 println!("{}", std::mem::size_of::<GoodAlign>()); // 例えば、8
フィールドの順序は、アクセスのパフォーマンスに影響を与えますか?
順序そのものは、フィールドへのアクセス速度には影響しません。しかし、低レベルで構造体をシーケンシャルに走査する場合(たとえば、SIMD命令を使用する場合や、構造体の配列をループで処理する場合)には、正しいアライメントがキャッシュの最適な利用を促進し、アクセスを高速化します。
列挙型の一つのバリエーションが非常に大きい場合、他のバリエーションがある場合でも、各列挙型のインスタンスは同じメモリを占有しますか?
はい、列挙型のサイズは常に最大のバリエーションのサイズにディスクリミネーターのサイズを加えたものです。任意のPacketは、Bのサイズを占有しますが、Aが含まれている場合でも同じです。
** ネガティブケース
構造体にはu8フィールドがあり、その後にu64があります。100000レコードの配列を使用すると、パディングのために最大1GBのメモリが消費されます。
利点:
欠点:
** ポジティブケース
構造体をフィールドの幅に従ってソートし、大きなバリエーションの列挙型はBoxに持ち込み、小さなものはそのままにしました。
利点:
欠点: