注: 以下の翻訳の正確性は検証されていません。AIPを利用して英語版の原文から機械的に翻訳されたものです。

ネイティブアクセラレーション

Velox ↗ を使用してネイティブアクセラレーションを有効にすることで、Spark のパフォーマンスを向上させることができます。

ネイティブアクセラレーションは、バッチジョブのパフォーマンスを向上させるために低レベルのハードウェア最適化を利用する技術です。これらのパフォーマンス向上は、Java仮想マシン (JVM) 言語からC++のようなネイティブ言語にコンピュートを移行することによって達成されます。これらの言語は機械語にコンパイルされ、マシンのハードウェア上で直接実行されます。プラットフォーム固有の機能を使用することで、ネイティブアクセラレーションは大規模なデータワークロードを処理するために必要な時間を大幅に短縮し、ジョブの実行を高速化し、リソースの利用効率を改善することを目指しています。

ネイティブアクセラレーションは、PythonトランスフォームPipeline Builder で利用可能です。

ビルド分析

ネイティブアクセラレーションされたビルドの基本的な分析は、Spark Details ページで行うことができます。Query Plan タブの下で、Physical Plan を選択すると、次のようなものが表示されます。

// 物理プランの構造を示しています
== Physical Plan ==
AdaptiveSparkPlan  // 適応型のSparkプラン
+- == Final Plan ==
   Execute InsertIntoHadoopFsRelationCommand  // Hadoopファイルシステムへの挿入コマンドを実行
   +- WriteFiles  // ファイル書き込み操作
      +- CollectMetrics  // メトリクスの収集
         +- VeloxColumnarToRowExec  // Veloxのカラム形式から行形式への変換
            +- ^ ProjectExecTransformer  // プロジェクト実行トランスフォーマー
               +- ^ InputIteratorTransformer  // 入力イテレーターのトランスフォーマー
                  +- ^ InputAdapter  // 入力アダプター
                     +- ^ RowToVeloxColumnar  // 行形式からVeloxカラム形式への変換
                        +- ^ HashAggregate  // ハッシュ集計操作
                           +- ^ VeloxColumnarToRowExec  // Veloxのカラム形式から行形式への変換
                              +- ^ AQEShuffleRead  // AQE(Adaptive Query Execution)のシャッフル読み取り
                                 +- ^ ShuffleQueryStage  // シャッフルクエリステージ
                                    +- ColumnarExchange  // カラム形式のデータ交換
                                       +- ^ ProjectExecTransformer  // プロジェクト実行トランスフォーマー
                                          +- ^ RowToVeloxColumnar  // 行形式からVeloxカラム形式への変換
                                             +- * ColumnarToRow  // カラム形式から行形式への変換
                                                +- BatchScan parquet  // Parquet形式のバッチスキャン

このコードは、Spark SQLの物理プランを示しています。各ノードはデータ処理のステップを表しており、データ形式の変換やシャッフル操作などが含まれています。 通常のSparkクエリプランに似ていますが、いくつかの重要な違いがあります。ProjectExecノードの代わりに、ProjectExecTransformerがあります。これにより、操作はVeloxクエリエンジンでネイティブに実行されることを意味します。クエリプランのすべてのオフロードされたノードは、ツリー内で^記号でマークされます。ネイティブ実行のブロックは、RowToVeloxColumnarVeloxColumnarToRowExecに挟まれています。これらのノードは、SparkデータセットをArrow DataFramesに変換し、逆も行います。このシリアル化/デシリアル化にはかなりのコストがかかります。

ネイティブアクセラレーションのパフォーマンスが低いことを示す一般的なパターンは2つあります:

  • ^記号で示されるように、ネイティブに実行されるノードの割合が小さい。
  • 多数のRowToVeloxColumnarVeloxColumnarToRowExecノードがあり、高いシリアル化オーバーヘッドをもたらす。

この分析は、パフォーマンスが期待どおりでない場合に役立ちます。パイプラインに小さな変更を加えることで、オフロードされる計算量に大きな影響を与えることができます。チェックポイントのような機能を使用して、ネイティブにすべて実行できるビルドのチャンクを手動でグループ化することができます。

ネイティブアクセラレーションの実装とアーキテクチャ

Foundryのネイティブアクセラレーションの実装は、Apache Gluten ↗プロジェクトに基づいています。Foundryのネイティブアクセラレーションは、Velox ↗クエリエンジンを利用して、実行時にSparkジョブを高速化します。VeloxはC++で書かれており、データベースアクセラレーションを念頭に置いて設計されています ↗。Arrow DataFrames上でオペレーターレベルの操作を実行するための開発者APIを提供しています。Glutenは、SparkランタイムとVeloxを結びつけるために必要な「グルー」を提供します。

このセットアップでは、パイプラインは最初に通常のビルド(ネイティブアクセラレーションなし)と同様にSparkクエリプランを生成します。その後、追加の最適化ルールがプランに適用され、クエリの一部がVeloxで実行できるかどうかが識別されます。この決定は、Veloxに同等の実装があるかどうか、Glutenに実装のマッピングが存在するかどうかに基づいています。クエリはオペレーターレベルでオフロードできます。これは、SELECTFILTERJOINのようなSQLステートメントに大まかに対応します。オフロード可能なクエリプランの部分は、この段階でマークされます。

プランニングステップが完了すると、クエリは通常のSparkエンジンを通じて実行されます。これは、すべてのタスクスケジューリング、エグゼキュータオーケストレーション、およびライフサイクル管理が通常どおり進行することを意味します。違いは、エグゼキュータがネイティブ実行用にマークされたクエリプランの部分に到達したときに生じます。この場合、Sparkのデフォルトの実装を呼び出す代わりに、Veloxの実装が呼び出されます。

このアーキテクチャは、すべての計算がVeloxで行えないクエリをサポートするため、特に有利です。これは、オフロードの決定がプラン全体ではなくオペレーターレベルで行われるためです。サポートされるオペレーターの数は絶えず増加していますが、UDFのようなユーザー作成のコードは、ネイティブ実装が存在しないため、オフロードされることはありません。

サポートされているオペレーターと式の完全なリストを表示 ↗

なぜネイティブアクセラレーションは速いのか?

SparkはScalaで書かれたJVM言語であり、そのパフォーマンスを向上させるためにコード生成 ↗などの多くの最適化を含んでいます。さらに、JVM自体には、できるだけ多くのプラットフォーム固有の機能を活用することを目的としたC2コンパイラ ↗などの最適化が含まれています。しかし、C++のようなネイティブ言語は、3つの基本的な理由で引き続き優れたパフォーマンスを提供します。

  • コンパイル時の最適化: JavaとScalaはバイトコードにコンパイルされ、その後JVMによって実行されますが、C++のようなネイティブ言語は直接機械語にコンパイルされます。これにより、C++コンパイラは実行時のオーバーヘッドを大幅に削減するコンパイル時の最適化を行うことができます。対照的に、JVM言語は実行中に行われるJIT(Just-In-Time)コンパイルに依存しており、実行を迅速に開始する必要とのバランスをとるため、同じレベルの最適化を達成できない場合があります。
  • ガベージコレクション(GC)がない: C++ではメモリ管理が手動で行われるため、ガベージコレクション(GC)に関連するオーバーヘッドが排除されます。JVM言語では、GCプロセスが予測不可能な停止やオーバーヘッドを引き起こす可能性があり、特にメモリを多く使用するアプリケーションではパフォーマンスに影響を与える可能性があります。
  • ハードウェアへの直接アクセスとベクトル化APIの利用: C++はハードウェア機能や低レベルのシステムリソースへの直接アクセスを提供し、開発者がSSE、AVX、およびその他のSIMD(Single Instruction Multiple Data)命令などのプラットフォーム固有の最適化とベクトル化APIを活用できるようにします。この直接アクセスにより、JVM言語では抽象化レイヤーが同じレベルのハードウェアの相互作用を妨げる可能性があるため、微調整されたパフォーマンス最適化が可能になります。

ネイティブアクセラレーションのメモリ設定に関する考慮事項

Foundryでネイティブアクセラレーションを使用してSparkを実行するには、通常のバッチパイプラインとは少し異なる設定が必要です。Sparkは、いくつかの操作をオフヒープメモリ ↗で実行することをサポートしています。オフヒープメモリはJVMによって管理されないメモリであり、GCのオーバーヘッドを排除し、パフォーマンスを向上させます。デフォルトでは、Foundryではオフヒープメモリを有効にしていません。これを有効にすると、パイプラインの追加のメンテナンスコストが発生する可能性があるためです。ネイティブアクセラレーションにはオフヒープメモリが必要です。Veloxによって変更されたDataFrameは、ネイティブプロセスによってアクセス可能であるためにオフヒープでなければなりません。Foundryは、Veloxのデータトランスフォーメーションを除くすべてのために十分なオンヒープメモリを必要とします(たとえば、オーケストレーション、スケジューリング、およびビルド管理コードは依然としてJVMで実行されます)が、理想的にはほとんどの作業がオフヒープで実行されることになります。ネイティブアクセラレーションを使用するようにパイプラインを設定すると、オンヒープメモリとオフヒープメモリのバランスを取る上で追加のメンテナンスコストが発生します。