rustc におけるメモリ管理
一般に、rustc はメモリの管理方法についてかなり慎重であろうとします。 コンパイラはコンパイル全体を通じて 非常に多くの データ構造を割り当てるため、 注意しないと、それに多くの時間と空間がかかってしまいます。
コンパイラがこれを管理する主な方法の 1 つは、arena と interning を使用することです。
アリーナと インターン化
コンパイル中には非常に多くのデータ構造が作成されるため、パフォーマンス上の
理由から、それらをグローバルなメモリプールから割り当てます。
それぞれは長命な アリーナ から一度だけ割り当てられます。
これは アリーナ割り当て と呼ばれます。
この仕組みにより、メモリの割り当て/解放が削減されます。
また、等価性のための型(型について詳しくはこちら)の比較も容易になります。
インターン化された各型 X について、X に対する PartialEq を実装しているため、
単にポインタを比較できます。
CtxtInterners 型には、インターン化された型のマップ群とアリーナ自体が含まれます。
例: ty::TyKind
コンパイラ内の型を表す ty::TyKind の例を取り上げます(詳しくはこちらを
読めます)。 型を構築したいとき、コンパイラは単純にバッファから割り当てることは
しません。 代わりに、その型がすでに構築されているかどうかを確認します。すでに構築されて
いれば、以前と同じポインタを取得するだけです。そうでなければ新しいポインタを作成します。
この方式では、2 つの型が同じかどうかを知りたい場合、必要なのはポインタを比較することだけで、
これは効率的です。 ty::TyKind は決してスタック上で構築すべきではなく、そうした場合は
使用できません。
常にこのアリーナからそれらを割り当て、常にそれらをインターン化することで、それらは
一意になります。
コンパイルの開始時にバッファを作成し、型を割り当てる必要があるたびに、このメモリバッファの一部を使用します。
空間が足りなくなった場合は、別のバッファを取得します。そのバッファのライフタイムは
'tcx です。私たちの型はそのライフタイムに結び付けられているため、コンパイルが終了すると、そのバッファに関連する
すべてのメモリが解放され、'tcx 参照は無効になります。
型に加えて、割り当て可能なアリーナ割り当てのデータ構造は他にも多数あり、 それらはこのモジュール内にあります。以下にいくつか例を示します。
GenericArgsはmk_argsで割り当てられます – これは型のスライスをインターン化し、多くの場合 ジェネリック引数に代入される値を指定するために使用されます(例:HashMap<i32, u32>は スライス&'tcx [tcx.types.i32, tcx.types.u32]として表されます)。TraitRefは通常値渡しされます – トレイト参照 は、トレイトへの参照と そのさまざまな型パラメータ(Selfを含む)からなります。たとえばi32: Displayのようなものです(ここでは、def-id はDisplayトレイトを参照し、args にはi32が含まれます)。なお、def-idはAdtDefとDefIdセクションで定義され、詳しく説明されています。Predicateは、トレイトシステムが証明しなければならないものを定義します(traits モジュールを参照)。
tcx とそのライフタイムの使い方
型付けコンテキスト (tcx) はコンパイラ内の中心的なデータ構造です。これは、
あらゆる種類のクエリを実行するために使用するコンテキストです。struct TyCtxt は、この共有
コンテキストへの参照を定義します。
tcx: TyCtxt<'tcx>
// ----
// |
// アリーナのライフタイム
お分かりのように、TyCtxt 型はライフタイムパラメータを取ります。
'tcx のようなライフタイムを持つ参照を見た場合、それはアリーナ割り当てされたデータ
(または、いずれにせよアリーナと同じだけ長く生きるデータ)を参照していることを意味します。
ライフタイムに関する注記
Rust コンパイラはかなり大規模なプログラムであり、多数の大きなデータ
構造(例: 抽象構文木 (AST)、高水準中間
表現 (HIR)、および型システム)を含んでいます。そのため、不要なメモリ使用を
最小限に抑えるために、アリーナと参照に大きく依存しています。これは、
人々がコンパイラに組み込む方法(つまり
ドライバー)にも現れており、より Rust らしい “pull” スタイル
(Iterator トレイトを考えてください)ではなく、“push” スタイルの API(コールバック)が好まれます。
スレッドローカルストレージとインターン化は、重複を減らしつつ、
広範に存在する多数のライフタイムに起因する使い勝手上の問題の多くを防ぐために、
コンパイラ全体で多用されています。これらのスレッドローカルにアクセスするには
rustc_middle::ty::tls モジュールが使用されますが、触れる必要はほとんどないはずです。