この記事は、The Parallel Universe Magazine 47 号に掲載されている「Vectorization in LLVM and GCC for Intel CPUs and GPUs」の日本語参考訳です。原文は更新される可能性があります。原文と翻訳文の内容が異なる場合は原文を優先してください。
近年の CPU や GPU のコアは、SIMD (Single Instruction, Multiple Data) 実行ユニットを利用して、より高いパフォーマンスと電力効率を実現しています。SIMD ハードウェアは、インテル® SSE、インテル® AVX、インテル® AVX2、インテル® AVX-512、およびインテル® Xe アーキテクチャー (第 12 世代) ISA などの命令を介して利用されます。これらを直接使用することも可能ですが、低レベルのため移植性が著しく制限され、ほとんどのプロジェクトでは推奨されません。
ここでは、移植性が高く、使いやすいインターフェイスをプログラマーに提供するため、自動ベクトル化、言語構造やプラグマなどのヒントを使用した SIMD ベクトル化、および SIMD データ並列ライブラリー・アプローチの 3 つの手法を検討します。これらの手法の概要を説明し、コード例を使用して LLVM および GCC コンパイラーにおける SIMD ベクトル化の進化を紹介します。また、インテル® Xeon® プロセッサーおよびインテル® Xe アーキテクチャー GPU で最適なパフォーマンスを実現するため、LLVM および GCC コンパイラーにおける 2 つのベクトル化手法を検討します。
LLVM と GCC の拡張
インテルは、LLVM と GCC コンパイラーの両方のベクトル化機能を強化することを目標としており、オープンソースへの貢献は重要な設計上の考慮事項でした。VPlan ベクトライザーと関連する VectorABI は、LLVM と GCC[13] の両方のオプティマイザーに統合できるように設計されています。
VPlan ベクトライザーのフレームワークは、LLVM トランクに統合される可能性があります。インテルの VectorABI[12] は公開されており、LLVM および GCC コミュニティーによって関数のベクトル化に利用されています。VPlan ベクトライザーは、インテル® Xeon® プロセッサー用のインテル® コンパイラーが以前提供していた結果を上回っています。
SIMD の利用
近年の CPU は SIMD 実行をサポートしています。SIMD とは、1 つの命令で複数のデータ要素を並列に実行するハードウェア機能です。制御フローが似ており (ベクトルの発散が少ない)、メモリーに依存しない複数のデータを一度に操作する場合に有効です。残念ながら、SIMD ISA を直接利用するプログラムを記述することは容易ではなく、移植性にも限界があります。この状況を改善するため、自動ベクトル化、ヒントや言語構造を使用したプログラマーによる SIMD ベクトル化、および C++ SIMD データ並列ライブラリーの利用という 3 つのアプローチを説明します。
自動ベクトル化
データと制御の依存関係解析を自動的に行い、組込みコストモデルに基づいてスカラープログラムをベクトル形式に変換することを自動ベクトル化[4][5] と呼びます。この方法は生産性や移植性に優れており、プログラマーにとって魅力的ですが、ループ境界やメモリー・アクセス・パターンなど、コンパイル時に未知な部分があるため、自動ベクトル化は必ずしも最適なコードを生成するわけではありません。
プログラマーによる SIMD ベクトル化
OpenMP* (バージョン 4.0 以降) には、ベクトルレベルの並列処理をサポートする SIMD 構文があります[7]。これらの構文は、標準化されたベクトル化構文のセットを提供するため、プログラマーは移植性のないベンダー固有の組込み関数やディレクティブを使用する必要がありません[6]。さらに、これらの構文はコンパイラーにコードの構造に関する追加のヒントを提供し、並列化とうまく融合した、より優れたベクトル化を可能にします[5]。
C++ SIMD データ並列ライブラリー
ISO C++ にはデータ並列ライブラリーに関する提案が提出されており[3]、その目的は、SIMD レジスターや命令、共通の命令デコーダーで実行される実行ユニットなどのデータ並列実行リソースによる高速化をサポートすることです。これらの実行リソースが利用できない場合、インターフェイスはシーケンシャル実行への透過的なフォールバックをサポートします。図 1 に、C++ SIMD データ並列ライブラリーを使用した SIMD memcpy の例を示します。この例をコンパイルして、core-avx512 用の LLVM ベクトル中間表現 (IR) とバイナリーを生成できます。
namespace stdsimd = std::experimental; void simd_memcpy( stdsimd::native_simd<float> x, stdsimd::native_simd<float> y, void *p) { auto cmp = x < y; memcpy(p, &cmp, cmp.size()*4); } |
define void @_Z11simd_memcpy_Pv(<16 x float> %x.coerce, <16 x float> %y.coerce, i8* nocapture %p) { entry: %0 = fcmp fast olt <16 x float> %x.coerce, %y.coerce %cmp.sroa.0.sroa.0.0.p.sroa_cast = bitcast i8* %p to <16 x i1>* store <16 x i1> %0, <16 x i1>* %cmp.sroa.0.sroa.0.0.p.sroa_cast ret void } |
図 1. C++ SIMD データ並列ライブラリーの例
SIMD ベクトル化は、どのベクトル化手法を使用して SIMD コードを生成するかにかかわらず、最新の CPU および GPU 上で計算集約型ワークロードの最適なパフォーマンスを実現するために非常に重要です。次のセクションでは、いくつかのコード例とともに、CPU と GPU に関する最近の LLVM SIMD ベクトル化の進歩を紹介します。
製品とパフォーマンス情報
1実際の性能は利用法、構成、その他の要因によって異なります。詳細については、www.Intel.com/PerformanceIndex (英語) を参照してください。