スカイフレーム

Bazel の並列評価とインクリメンタリティ モデル。

データモデル

データモデルは次の項目で構成されます。

  • SkyValue。ノードとも呼ばれます。SkyValues は、ビルドの過程でビルドされたすべてのデータとビルドの入力を格納する不変オブジェクトです。例: 入力ファイル、出力ファイル、ターゲット、構成済み ターゲット。
  • SkyKeySkyValue を参照するための短い不変の名前。たとえば、 FILECONTENTS:/tmp/fooPACKAGE://foo などです。
  • SkyFunction。キーと依存ノードに基づいてノードをビルドします。
  • ノードグラフ。ノード間の依存関係を含むデータ構造 。
  • Skyframe。Bazel の基盤となる増分評価フレームワークのコード名。

評価

ビルドは、ビルドリクエストを表すノードを評価することで実現されます。

まず、Bazel は最上位の SkyKey のキーに対応する SkyFunction を見つけます。次に、この関数は、最上位ノードを評価するために必要なノードの評価をリクエストします。これにより、リーフノードに到達するまで他の SkyFunction 呼び出しが行われます。リーフノードは通常、ファイル システム内の 入力ファイルを表すノードです。最終的に、Bazel は 最上位の 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 回目のビルドの小さな グラフを走査するだけのトップダウンの無効化とは異なり、最初のビルドの大きなグラフ が無効になります。

Bazel はボトムアップの無効化のみを行います。

インクリメンタリティをさらに高めるために、Bazel は 変更のプルーニングを使用します。ノードが 無効になったが、再ビルド時に新しい値が古い値と同じであることが判明した場合、このノードの変更によって無効になったノードは 「復活」します。

これは、たとえば、C++ ファイルのコメントを変更した場合に便利です。この場合、生成される .o ファイルは同じになるため、 リンカーを再度呼び出す必要はありません。

増分リンク / コンパイル

このモデルの主な制限事項は、ノードの無効化が オール オア ナッシングであることです。依存関係が変更されると、変更に基づいてノードの古い値を変更する優れたアルゴリズムが存在する場合でも、依存ノードは常に 最初から再ビルドされます。この方法が役立つ例をいくつか示します 。

  • 増分リンク
  • JAR ファイル内の 1 つのクラスファイルが変更された場合、最初からビルドし直すのではなく、JAR ファイルをインプレースで 変更できます。

Bazel がこれらのことを原則としてサポートしていない理由は 2 つあります。

  • パフォーマンスの向上が限定的だった。
  • 変更の結果がクリーンな再ビルドの結果と同じであることを検証することが難しく、Google はビット単位で再現可能なビルドを重視している。

これまで、コストのかかる ビルドステップを分解し、部分的な再評価を行うことで、十分なパフォーマンスを実現できました。たとえば、Android アプリでは、すべてのクラスを複数のグループに分割して、個別に dex できます。このようにすると、グループ内のクラスが変更されていない場合、dexing をやり直す必要は ありません。

Bazel のコンセプトへのマッピング

これは、Bazel がビルドを実行するために使用する主要な SkyFunctionSkyValue 実装の概要です。

  • FileStateValuelstat() の結果。既存のファイルの場合、関数はファイルの変更を検出するために追加情報も計算します。これは Skyframe グラフの最下位レベルのノードであり、依存関係は ありません。
  • FileValue 。ファイルの実際のコンテンツまたは 解決済みパスに関心があるものによって使用されます。対応する FileStateValue と、解決する必要があるシンボリック リンク(a/bFileValue には、a の解決済みパスと a/b の解決済みパスが必要)に依存します。FileValueFileStateValue の違いは、後者がファイルのコンテンツが実際に必要ない場合に使用できることです。たとえば、ファイル システムの glob(srcs=glob(["*/*.java"]) など)を評価する場合、ファイルの内容は 関係ありません。
  • DirectoryListingStateValuereaddir() の結果。 FileStateValue と同様に、これは最下位レベルのノードであり、依存関係はありません。
  • DirectoryListingValue。ディレクトリの エントリに関心があるものによって使用されます。対応する DirectoryListingStateValue と、ディレクトリの関連する FileValue に依存します。
  • PackageValue 。BUILD ファイルの解析済みバージョンを表します。関連するBUILDファイルのFileValueに依存し、パッケージ内の glob を解決するために使用される DirectoryListingValue(内部的にBUILDファイルの内容を表すデータ構造)にも推移的に依存します。
  • ConfiguredTargetValue。構成済みターゲットを表します。これは、ターゲットの分析中に生成されたアクションのセットと 依存する構成済みターゲットに提供される情報のタプルです。対応するターゲットが存在する PackageValue、直接依存関係のConfiguredTargetValues 、ビルド 構成を表す特別なノードに依存します。
  • ArtifactValue。ビルド内のファイルを表します。ソース アーティファクトまたは 出力アーティファクトです。アーティファクトはファイルとほぼ同じで、ビルドステップの実際の実行中にファイルを参照するために 使用されます。ソースファイル は関連ノードの FileValue に依存し、出力アーティファクト はアーティファクトを生成する アクションの ActionExecutionValue に依存します。
  • ActionExecutionValue。アクションの実行を表します。入力ファイルの ArtifactValues に依存します。実行するアクションは SkyKey に含まれています。これは、SkyKey は小さくすべきであるというコンセプトに反しています。実行フェーズが実行されない場合、ActionExecutionValueArtifactValue は使用されません。

視覚的な補助として、次の図は Bazel 自体のビルド後の SkyFunction 実装間の関係を示しています。

SkyFunction 実装の関係のグラフ