スカイフレーム

問題を報告 ソースを表示

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

データモデル

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

  • SkyValue。ノードとも呼ばれます。SkyValues は、ビルドの過程で構築されたすべてのデータとビルドの入力を含む不変のオブジェクトです。例: 入力ファイル、出力ファイル、ターゲット、構成されたターゲット。
  • SkyKeySkyValue を参照する不変の短い名前(例: FILECONTENTS:/tmp/fooPACKAGE://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 はビット単位の再現性のあるビルドを重視しています。

これまでは、高価なビルドステップを分解して部分的な再評価を行うだけで、常に十分なパフォーマンスを達成できました。つまり、アプリのすべてのクラスを複数のグループに分割し、それらに対して個別に dex 変換を行うだけでした。これにより、グループ内のクラスが変化しなくても、dex 変換をやり直す必要はありません。

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

以下に、Bazel がビルドの実行に使用する SkyFunction 実装の一部の概要を示します。

  • FileStateValue を返します。lstat() の結果。既存のファイルについては、ファイルの変更を検出するための追加情報も計算されます。これは Skyframe グラフの最下位ノードで、依存関係はありません。
  • FileValue。ファイルの実際の内容や解決されたパスを考慮するすべてのもので使用されます。対応する FileStateValue と解決する必要があるシンボリック リンクによって異なります(a/bFileValue には、a の解決済みパスと a/b の解決済みパスが必要です)。FileStateValue の区別は、ファイル システムの glob を評価する場合など(srcs=glob(["*/*.java"]) など)、ファイルのコンテンツが実際には不要な場合があるため、重要です。
  • DirectoryListingValue。基本的には readdir() の結果です。ディレクトリに関連付けられている FileValue によって異なります。
  • PackageValue。BUILD ファイルの解析済みバージョンを表します。関連する BUILD ファイルの FileValue と、パッケージ内の glob の解決に使用される DirectoryListingValue(内部で BUILD ファイルの内容を表すデータ構造)に依存します。
  • ConfiguredTargetValue。構成されたターゲットを表します。これは、ターゲットの分析中に生成されたアクションのセットと、このアクションに依存する構成済みのターゲットに提供される情報のタプルです。対応するターゲットが存在する PackageValue に応じて、直接依存関係の ConfiguredTargetValues、ビルド構成を表す特別なノード。
  • ArtifactValue。ソースまたは出力アーティファクトで、ビルド内のファイルを表します(アーティファクトはファイルにほぼ相当し、ビルドステップの実際の実行時にファイルを参照するために使用されます)。ソースファイルの場合は、関連するノードの FileValue に依存し、出力アーティファクトの場合は、アーティファクトを生成するアクションの ActionExecutionValue に依存します。
  • ActionExecutionValue。アクションの実行を表します。入力ファイルの ArtifactValues によって異なります。実行するアクションは現在 sky キー内に含まれています。これは、sky キーが小さくなければならないという概念に反しています。現在、この不一致の解決に取り組んでいます(Skyframe で実行フェーズを実行しない場合、ActionExecutionValueArtifactValue は使用されません)。