Bazel の並列評価と増分モデル。
データモデル
データモデルは次の項目で構成されます。
SkyValue
。ノードとも呼ばれます。SkyValues
は、ビルド中にビルドされたすべてのデータとビルドの入力を含む不変のオブジェクトです。例: 入力ファイル、出力ファイル、ターゲット、構成済み できます。SkyKey
。SkyValue
を参照する短い不変の名前(FILECONTENTS:/tmp/foo
、PACKAGE://foo
など)。SkyFunction
。キーと依存ノードに基づいてノードをビルドします。- ノードグラフ。ノード間の依存関係を含むデータ構造。
Skyframe
。Bazel が基盤とする増分評価フレームワークのコード名。
評価
ビルドでは、ビルド リクエストを表すノードを評価します(この状態を目指していますが、多くのレガシーコードがその妨げになっています)。まず、その 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
で単一の.class
ファイルが変更された場合、理論上は、.jar
ファイルをゼロから再度ビルドするのではなく、ファイルを変更することもできます。
Bazel が現時点でこれらのことを原則的にサポートしていない理由(増分リンクはある程度サポートしていますが、Skyframe 内には実装していません)には 2 つの理由があります。パフォーマンスの向上は限定的であり、ミューテーションの結果がクリーンな再ビルドと同じになることを保証するのは困難であり、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
と、パッケージ内の glob の解決に使用されるDirectoryListingValue
(内部でBUILD
ファイルの内容を表すデータ構造)に依存します。 - ConfiguredTargetValue。構成されたターゲットを表します。これは、ターゲットの分析中に生成されたアクションのセットと、このアクションに依存する構成済みのターゲットに提供される情報のタプルです。対応するターゲットが存在する
PackageValue
に応じて、直接依存関係のConfiguredTargetValues
、ビルド構成を表す特別なノード。 - ArtifactValue。ソースまたは出力アーティファクトで、ビルド内のファイルを表します(アーティファクトはファイルにほぼ相当し、ビルドステップの実際の実行時にファイルを参照するために使用されます)。ソースファイルの場合は、関連するノードの
FileValue
に依存します。出力アーティファクトの場合は、アーティファクトを生成するアクションのActionExecutionValue
に依存します。 - ActionExecutionValue。アクションの実行を表します。入力ファイルの
ArtifactValues
に依存します。実行するアクションは現在 sky キー内に含まれています。これは、sky キーが小さくなければならないという概念に反しています。現在、この不一致の解決に取り組んでいます(Skyframe で実行フェーズを実行しない場合、ActionExecutionValue
とArtifactValue
は使用されません)。