注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。
Velox ↗ によるネイティブアクセラレーションを有効にすると、Spark のパフォーマンスを向上させることができます。
ネイティブアクセラレーションは、低レベルのハードウェア最適化を活用してバッチジョブのパフォーマンスを向上させる手法です。このパフォーマンス向上は、計算処理を Java 仮想マシン(JVM)言語から、C++ などのネイティブ言語に移すことで実現します。ネイティブ言語はマシンコードにコンパイルされ、マシンのハードウェア上で直接実行されます。ネイティブアクセラレーションは、プラットフォーム固有の機能を利用することで、大規模なデータワークロードの処理に必要な時間を大幅に短縮し、ジョブの実行を高速化してリソース使用効率を向上させることを目指しています。
ネイティブアクセラレーションは、Python トランスフォームおよび Pipeline Builder で利用可能です。
Spark の詳細ページで、ネイティブアクセラレーションを使用したビルドの基本的な分析を行うことができます。クエリプランタブで物理プランを選択すると、次のような内容が表示されます。
== Physical Plan ==
AdaptiveSparkPlan
+- == Final Plan ==
Execute InsertIntoHadoopFsRelationCommand
+- WriteFiles
+- CollectMetrics
+- VeloxColumnarToRowExec
+- ^ ProjectExecTransformer
+- ^ InputIteratorTransformer
+- ^ InputAdapter
+- ^ RowToVeloxColumnar
+- ^ HashAggregate
+- ^ VeloxColumnarToRowExec
+- ^ AQEShuffleRead
+- ^ ShuffleQueryStage
+- ColumnarExchange
+- ^ ProjectExecTransformer
+- ^ RowToVeloxColumnar
+- * ColumnarToRow
+- BatchScan parquet
従来の Spark クエリプランとおおむね似ていますが、いくつかの重要な違いがあります。ProjectExec ノードの代わりに、ProjectExecTransformer があります。これは、その演算が Velox クエリエンジンでネイティブ実行されることを意味します。クエリプランでオフロードされるすべてのノードには、ツリー内で ^ 記号が付けられます。ネイティブ実行のブロックは、RowToVeloxColumnar と VeloxColumnarToRowExec に挟まれています。これらのノードは、Spark データセットから Arrow DataFrame への変換と、その逆方向の変換を担います。このシリアライズとデシリアライズには、大きなコストがかかります。
一般に、ネイティブアクセラレーションのパフォーマンスが低いことを示すパターンは2つあります。
^ 記号で示される、ネイティブ実行されるノードの割合が小さい。RowToVeloxColumnar および VeloxColumnarToRowExec ノードが多数あり、シリアライズのオーバーヘッドが大きい。この分析は、パフォーマンスが期待どおりでない場合に役立ちます。パイプラインへの小さな変更が、オフロードされる計算処理の量に大きく影響することがあります。Checkpoint などの機能を使用すると、すべてをネイティブ実行できるビルドの部分を手動でまとめることができます。
Foundry のネイティブアクセラレーションの実装は、Apache Gluten ↗ プロジェクトを基盤としています。Foundry のネイティブアクセラレーションは、Velox ↗ クエリエンジンを活用して、実行時に Spark ジョブを高速化します。Velox は C++ で記述されており、データベースの高速化を明確に念頭に置いて設計されています ↗。また、Arrow DataFrame ↗ に対して演算子レベルの演算を実行するための開発者向け API を提供します。Gluten は、Spark ランタイムと Velox を結び付けるために必要な「接着剤」の役割を果たします。
この構成では、パイプラインはまず、通常のビルド(ネイティブアクセラレーションを使用しないビルド)と同様に Spark クエリプランを生成します。次に、追加の最適化ルールがプランに適用され、クエリの一部を Velox で実行できるかどうかが判定されます。この判定は、Velox に同等の実装があるかどうか、およびその実装へのマッピングが Gluten に存在するかどうかに基づきます。クエリは演算子レベルでオフロードできます。これは、おおよそ SELECT、FILTER、JOIN などの SQL 文に相当します。この段階で、クエリプランのうちオフロードできるすべての部分に印が付けられます。
プランニングのステップが完了すると、クエリは通常の Spark エンジンを通じて実行されます。つまり、すべてのタスクのスケジューリング、エグゼキューターのオーケストレーション、ライフサイクル管理は通常どおり進みます。違いが生じるのは、エグゼキューターが、ネイティブ実行の対象として印が付けられたクエリプランの部分に到達したときです。この場合、Spark のデフォルトの実装を呼び出す代わりに、Velox の実装が呼び出されます。
このアーキテクチャは、計算処理の一部を Velox で実行できないクエリにも対応できるため、特に有利です。これは、オフロードの判定がプラン全体ではなく演算子レベルで行われるためです。サポートされる演算子の数は継続的に増えていますが、UDF のようなユーザーが作成したコードは、ネイティブ実装が存在しないため、オフロードできません。
Spark は JVM 言語である Scala で記述されており、パフォーマンスを向上させるためにコード生成 ↗など、多くの最適化を備えています。さらに、JVM 自体も、プラットフォーム固有の機能をできるだけ多く活用することを目指す C2 コンパイラー ↗などの最適化を備えています。しかし、C++ などのネイティブ言語は、次の3つの基本的な理由から、依然としてより高いパフォーマンスを発揮します。
Foundry でネイティブアクセラレーションを使用して Spark を実行するには、通常のバッチパイプラインとは少し異なる設定が必要です。Spark は、オフヒープメモリー ↗を使用した一部の演算の実行をサポートしています。オフヒープメモリーは JVM が管理しないメモリーであり、GC のオーバーヘッドをなくすことでパフォーマンスが向上します。オフヒープメモリーを有効にすると、パイプラインの保守コストが増える可能性があるため、Foundry ではデフォルトで有効にしていません。Velox によって変更される DataFrame は、ネイティブプロセスからアクセスできるようにオフヒープに配置する必要があるため、ネイティブアクセラレーションにはオフヒープメモリーの有効化が必要です。Foundry では、Velox によるデータ変換以外のすべての処理に、引き続き十分なオンヒープメモリーが必要です(たとえば、オーケストレーション、スケジューリング、ビルド管理のコードは引き続き JVM で実行されます)。ただし、理想的には、処理の大部分がオフヒープで実行されるようになります。ネイティブアクセラレーションを使用するようにパイプラインを設定すると、オンヒープメモリーとオフヒープメモリーのバランスを取るための保守コストが追加で発生します。