Bazel の並列評価と増分モデル。
データモデル
データモデルは次の項目で構成されます。
SkyValue
。ノードとも呼ばれます。SkyValues
は、ビルド中にビルドされたすべてのデータとビルドの入力を含む不変のオブジェクトです。例: 入力ファイル、出力ファイル、ターゲット、構成済みターゲット。SkyKey
。SkyValue
を参照する短い不変の名前(FILECONTENTS:/tmp/foo
、PACKAGE://foo
など)。SkyFunction
。キーと依存ノードに基づいてノードをビルドします。- ノードグラフ。ノード間の依存関係を含むデータ構造。
Skyframe
。Bazel が基盤とする増分評価フレームワークのコード名。
評価
ビルドは、ビルド リクエストを表すノードの評価で構成されます(これが Google が目指す状態ですが、多くのレガシー コードが障害になっています)。まず、その SkyFunction
が見つかり、最上位の SkyKey
のキーで呼び出されます。次に、関数はトップレベル ノードの評価に必要なノードの評価をリクエストします。これにより、リーフノード(通常はファイル システム内の入力ファイルを表すノード)に到達するまで、他の関数が呼び出されます。最終的に、トップレベルの SkyValue
の値、いくつかの副作用(ファイル システムの出力ファイルなど)、ビルドに関与したノード間の依存関係の有向非循環グラフが得られます。
SkyFunction
は、ジョブの実行に必要なすべてのノードを事前に特定できない場合、複数のパスで SkyKeys
をリクエストできます。簡単な例として、シンボリック リンクであることが判明した入力ファイルノードを評価する関数があります。この関数は、ファイルを読み取ろうとし、シンボリック リンクであることに気づき、シンボリック リンクのターゲットを表すファイル システム ノードを取得します。ただし、それ自体がシンボリック リンクである場合、元の関数はそのターゲットも取得する必要があります。
関数は、コードではインターフェース SkyFunction
で表され、SkyFunction.Environment
というインターフェースによって提供されるサービスです。関数は次のことができます。
env.getValue
を呼び出して、別のノードの評価をリクエストします。ノードが使用可能な場合はその値が返され、使用できない場合はnull
が返されます。関数自体はnull
を返す必要があります。後者の場合、依存ノードが評価され、元のノードビルダーが再度呼び出されます。ただし、同じenv.getValue
呼び出しはnull
以外の値を返します。env.getValues()
を呼び出して、他の複数のノードの評価をリクエストします。依存ノードが並列で評価される点を除き、基本的に同じ動作を行います。- 呼び出し中に計算を行う
- ファイル システムへのファイルの書き込みなど、副作用がある。2 つの異なる関数が互いに干渉しないように注意する必要があります。一般に、書き込み副作用(データが Bazel から外部に流れる)は問題ありませんが、読み取り副作用(登録済みの依存関係なしでデータが Bazel に内向きに流れる)は問題です。これは、登録されていない依存関係であるため、不正な増分ビルドが発生する可能性があるためです。
SkyFunction
の実装では、依存関係をリクエストする以外の方法(ファイル システムを直接読み取るなど)でデータにアクセスしないでください。そうしないと、読み取られたファイルに対するデータ依存関係が Bazel に登録されず、増分ビルドが正しく行われなくなります。
関数が処理を実行するのに十分なデータを取得したら、完了を示す null
以外の値を返す必要があります。
この評価戦略には次のようなメリットがあります。
- 気密性。関数が他のノードに依存する方法でのみ入力データをリクエストする場合、Bazel は入力状態が同じであれば同じデータが返されることを保証できます。すべての Sky 関数が確定的である場合、ビルド全体も確定的になります。
- 正確で完全なインクリメンタリティ。すべての関数のすべての入力データが記録されている場合、Bazel は入力データが変更されたときに無効にする必要があるノードセットのみを無効にできます。
- 並列処理。関数は依存関係をリクエストすることでのみ相互にやり取りできるため、相互に依存しない関数は並行して実行できます。また、Bazel では、関数が順番に実行された場合と同じ結果が得られることが保証されます。
インクリメンタリティ
関数は他のノードに依存することでのみ入力データにアクセスできるため、Bazel は入力ファイルから出力ファイルまでの完全なデータフローグラフを構築し、この情報を使用して、実際に再ビルドする必要があるノード(変更された入力ファイルのセットの逆推移閉包)のみを再ビルドできます。
特に、ボトムアップとトップダウンの 2 つの増分戦略があります。どちらが最適かは、依存関係グラフの外観によって異なります。
ボトムアップ無効化では、グラフが構築され、変更された入力のセットが判明した後、変更されたファイルに間接的に依存するすべてのノードが無効になります。これは、同じ最上位ノードが再びビルドされることがわかっている場合に最適です。ボトムアップの無効化では、変更されたかどうかを判断するために、前のビルドのすべての入力ファイルに対して
stat()
を実行する必要があります。inotify
などのメカニズムを使用して変更されたファイルを学習することで、この問題を改善できます。トップダウン無効化中は、最上位ノードの推移閉包がチェックされ、推移閉包がクリーンなノードのみが保持されます。これは、現在のノードグラフが大きいものの、次のビルドではそのサブセットのみが必要である場合に適しています。ボトムアップ無効化では、2 番目のビルドの小さなグラフを走査するトップダウン無効化とは異なり、最初のビルドの大きなグラフが無効になります。
現在のところ、ボトムアップの無効化のみを行っています。
さらなる増分処理を実現するために、変更のプルーニングを使用します。ノードが無効にされたが、再ビルド時に新しい値が古い値と同じであることが判明した場合、このノードの変更により無効にされたノードが「復活」します。
これは、たとえば C++ ファイル内のコメントを変更した場合に便利です。この場合、そこから生成される .o
ファイルは同じになるため、リンカーを再度呼び出す必要はありません。
増分リンク / コンパイル
このモデルの主な制限は、ノードの無効化が「すべてまたはなし」であるということです。依存関係が変更されると、変更に基づいてノードの古い値を変更するより優れたアルゴリズムが存在する場合でも、依存ノードは常に最初から再ビルドされます。たとえば、次のような場合に役立ちます。
- 増分リンク
.jar
で 1 つの.class
ファイルが変更された場合、理論的には、.jar
ファイルを最初からビルドし直すのではなく、.jar
ファイルを変更できます。
Bazel が現在、これらのことを原則的にサポートしていない理由は 2 つあります(増分リンクはある程度サポートしていますが、Skyframe には実装されていません)。パフォーマンスの向上は限定的であり、ミューテーションの結果がクリーンな再ビルドの結果と同じであることを保証するのが難しく、Google はビット単位で再現可能なビルドを重視しています。
これまでは、費用のかかるビルドステップを分解し、部分的な再評価を行うことで、常に十分なパフォーマンスを達成できました。アプリ内のすべてのクラスを複数のグループに分割し、それらを個別にデックス処理します。これにより、グループ内のクラスが変更されなければ、デコードをやり直す必要がなくなります。
Bazel コンセプトへのマッピング
以下は、Bazel がビルドの実行に使用する SkyFunction
実装の概要です。
- FileStateValue。
lstat()
の結果。既存のファイルについては、ファイルの変更を検出するために追加情報を計算します。これは Skyframe グラフの最下位ノードであり、依存関係はありません。 - FileValue。ファイルの実際の内容や解決済みパスを気にするすべてのものに使用されます。対応する
FileStateValue
と解決が必要なシンボリック リンク(a/b
のFileValue
にはa
の解決済みパスとa/b
の解決済みパスが必要など)によって異なります。FileStateValue
の区別は重要です。ファイル システムのグロブ(srcs=glob(["*/*.java"])
など)の評価など、ファイルの内容が実際に必要でない場合があります。 - DirectoryListingValue。基本的には
readdir()
の結果です。ディレクトリに関連付けられているFileValue
によって異なります。 - PackageValue。BUILD ファイルの解析されたバージョンを表します。関連する
BUILD
ファイルのFileValue
に依存し、パッケージ内のグロブの解決に使用されるDirectoryListingValue
(BUILD
ファイルの内容を内部で表すデータ構造)にも依存します。 - ConfiguredTargetValue。構成済みターゲットを表します。これは、ターゲットの分析中に生成された一連のアクションと、このターゲットに依存する構成済みターゲットに提供される情報の集合の Tuple です。対応するターゲットが含まれている
PackageValue
、直接依存関係のConfiguredTargetValues
、ビルド構成を表す特別なノードによって異なります。 - ArtifactValue。ビルド内のファイルを表します。ソース アーティファクトまたは出力アーティファクトのいずれかです(アーティファクトはファイルとほぼ同等で、ビルドステップの実際の実行中にファイルを参照するために使用されます)。ソースファイルの場合は、関連するノードの
FileValue
に依存します。出力アーティファクトの場合は、アーティファクトを生成するアクションのActionExecutionValue
に依存します。 - ActionExecutionValue。アクションの実行を表します。入力ファイルの
ArtifactValues
に依存します。現在、実行されるアクションはスカイキー内に含まれており、スカイキーは小さくする必要があるというコンセプトに反しています。現在、この差異の解消に取り組んでいます(Skyframe で実行フェーズを実行しない場合、ActionExecutionValue
とArtifactValue
は使用されません)。