前回、並列階層に対する概念的な説明をしましたが、今回は読者の皆様に具体的なイメージをつかんでいただくため、誤解を恐れず大胆に数値を仮定してシミュレーションをしてみることにします。結果の様子はできるだけよくある状況に合わせたつもりですが、個々の数字などはそのまま一人歩きされると困るものばかりですのでご注意ください。
今回は、ベクトル演算器やSIMD演算器の例についてあげ、関連技術としてNVIDIA社のGPGPUについても簡単に紹介します。
まずSIMD演算ですが、最近の多くのハイエンドCPUにSIMD演算器が適用されており、ニーズ拡大に伴い進歩しています。例えばIntel社はSSE (Streaming SIMD Extensions)からAVX (Advanced Vector Extensions)へ、ARM社はAdvanced SIMD extension (通称NEON)からSVE (Scalable Vector Extension)へと進化しており、それぞれのシリーズの中でも毎年のように進歩しています。
ベクトル演算器としては、ルネサスエレクトロニクス社の車載向けMCU RH850/U2Bに搭載されているNSITEXE社(現デンソー社)のDR1000CにはRISC-Vベクトル演算器が搭載されており(下図左)、またQualcomm社のHexagon NPU (Neural Processing Unit、下図右)にはベクトル演算器が搭載されています。ニューラルネットワーク処理の基本であるニューロンでの演算は積和演算であるため、大規模ニューラルネットワーク処理をベクトル演算器により効率よく実行することができます。
前回紹介したベクトル(SIMD)演算器に向けたコンパイル技術が自動ベクトル化です。自動ベクトル化は自動並列化(第16回参照)と似たような概念であり、広い意味では自動ベクトル化は自動並列化の一部ですが、狭い意味では異なります。自動並列化は複数のプロセッサ向けに処理を分割し、タスクやスレッドとして実行コードを生成する手法であるのに対し、自動ベクトル化はベクトル(SIMD)演算器に向けた機械語命令(例えば前回の図の中のアセンブリ言語命令simdadd)を含む実行コードを自動生成する手法であるためです。最近のgccやclangなどのコンパイラは自動ベクトル化機能を有しており、例えばIntelプロセッサの各コアに搭載されているSIMD演算器(SSE等)向けに実行コードを自動生成します。この場合、自動ベクトル化機能は複数のコアは使いません。
(狭義の)自動並列化と自動ベクトル化は排他的ではありません。自動並列化はタスクやスレッドといったオーバーヘッドが大きい並列化を扱うため処理単位を大きく取る方が効率的であり、自動ベクトル化は機械語命令レベルでの並列処理となります。そのため、どのレベルでも並列化が可能な多重ループがあるとき、最外ループで自動並列化を実施し、各コアに割り当てられた最内ループで自動ベクトル化を適用するといった高速化が可能です。この話は次の機会に説明します。
自動的にベクトル化する手法は、基本的にループ内の実行解析や依存解析になります。同時並列数が少ないSIMD演算器はCPUの内部または密結合で実装できオーバーヘッドを極めて小さくできるため、単純なループを高速実行する自動ベクトル化を実現できます。第16回において説明した自動並列化と同様、不用意にポインタ等を使わないことが重要です。すべてのループ回転において同じ機械語命令が同じ順序で実行でき、メモリ参照はループカウンタ変数の単純な式で表現できるとき、最大性能を発揮します。大規模行列計算にはベクトル化に向いた処理が多く現われます。ベクトル型スーパーコンピュータでは、データ転送オーバーヘッドは無視できませんが専用ハードウェアによるサポートがあり、比較的依存解析が容易なFORTRAN言語を使うことが歴史的に多く、コンパイラにわかりやすく並列をプログラミングするノウハウも蓄積されているため、古くから自動ベクトル化コンパイラが利用されています。
一方で、ループ回転ごとに異なる実行になるような場合や、配列の間接参照や複雑なループ終了判定があるような場合、解析が簡単ではなく、思うような性能が出ない場合もあります。そのような特殊処理や例外処理をサポートするベクトル(SIMD)対応機械語命令も合わせてサポートされていることは多いですが、複数データの同一命令並列実行が基本戦略である中でこれらの機械語コードを自動生成することは簡単ではありません。ソフトウェアから見たオーバーヘッドが極めて小さい特性を利用し、数命令程度の並列化を実現しようとしているときに、場合分け等でもたついただけで性能向上しない場合も多々あります。ベクトル(SIMD)演算の性能特性や種類はハードウェアアーキテクチャごとに異なり、それゆえ手動チューニングの効果が大きいこともあり、自動ベクトル化技術はスーパーコンピュータ向けを除いてはまだまだ発展途上です。現状、組込み向けでは行列計算や画像処理、AI処理に対し、ハードウェアベンダによりチューニングされたSIMD利用ライブラリ関数が提供されている場合が多く、そのようなライブラリ関数が使える処理であれば自動ベクトル化よりも簡単に高性能を実現することができます。
他方で、組込みシステムの性能向上の多くの部分をベクトル(SIMD)的な処理が占めるようになってきており、その高速・省エネルギー実行の短TAT開発のニーズが日々高まっています。ハードウェアアーキテクチャではRISC-Vにベクトル拡張命令が定義され(下図)、コンパイラ基盤のLLVMの中間言語LLVM-IRにはベクトル関連命令が定義されてきているなど、ベクトル化技術を支える基盤技術が近年著しく進歩しています。今後の自動ベクトル化の発展に期待されるところです。
今回はベクトル演算、SIMD演算の話です。ベクトル演算とSIMD演算は並列計算としては同種の高速化で、配列計算の一括処理です。マルチコアとは別の概念ですが、重要な並列技術ですのでこのブログでも取り上げたいと思います。画像処理やAI処理の高速省エネルギー実行に重要な技術であり最近話題のNPU (Neural Processing Unit)などにも使われています。NVIDIA社のGPGPUはマルチコアとSIMDとの中間的な技術ですが、同様に配列計算の一括処理であり、関連技術として次の機会に紹介します。
SIMDとはSingle-Instruction-Multiple-Dataの略で、複数のデータを同一の命令で処理するという意味です。通常のプロセッサの演算は下図の左の加算のように1組の数を足して1個の結果を得ますが、ベクトル演算やSIMD演算は、同時に複数組(図の例では4組)の数を足して複数個(図の例では4個)の結果を得るような処理です。なお、演算の名称はハードウェアベンダが名付けるものであり、学術的な定義はありません。歴史的にSIMD演算はIntelのSSEのように例えば4並列を1~数サイクル程度で実行するもの、ベクトル演算器はRISC-Vのベクトル拡張のように例えば64並列を数~10サイクル程度で実行するものに使われることが多いようです。
例年、EMS 組込みマルチコアサミット)を開催してきましたが、今年は少し形をかえて実施します。
11月21日(木) 13:40-14:10(予定)パシフィコ横浜にて開催されるEdgeTech+2024において
「組込みマルチコアコンソーシアムが見る組込み産業の課題」と題して枝廣が講演します。
詳しくは以下の記事をご参照下さい。
コンパイラはプログラムを動作させることなく解析するため、実行時にならないとわからない情報の解析には限界があります。その代表例がC言語のポインタです。ポインタの値はOS等が割り当てるメモリ番地であるため、実行時にならないとわかりません。以下で詳しく説明しますが、ポインタを利用(特に演算)すると、処理の依存関係(データの読み書き順序などの理由で生じる処理の実行順序関係)の解析が難しくなり、2つの処理A、Bの依存関係が不明という状況が生じます。本来、処理AとBには依存(実行順序の制約)がなく、並列に処理できるにもかかわらず、コンパイラの解析で不明になってしまうと、コンパイラは処理AとBを並列実行せず逐次的に実行する選択をせざるを得ないため、並列性能が下がってしまいます。
ポインタの話をする前に依存関係(正確にはデータ依存関係)について具体的な例を用いて説明します。依存関係とは、例えば下図左のように複数の処理が同じ変数に対して読み書きしており、その実行順序が結果(この場合には変数zの値)に影響するような場合をいいます。依存関係は並列化に重要な影響があり、この例の場合には処理Bは処理Aよりも必ず後に実行される必要があり、処理AとBは並列処理できません。下図右のように読み書きする変数が異なれば結果に影響することがなくなり、処理AとBは並列処理が可能になります。そのため、最大の並列性能向上を得るためには処理の依存関係を減らすことが重要です。依存関係を減らすためには、当然ながら処理のアルゴリズムにおいてできるだけ依存をなくすことが肝要ですが、今回は、依存関係が存在しないにも関わらずコンパイラの解析によって依存があるかもしれないと判断されてしまうケースについて考えます。
シングルコア向けに書かれたソフトウェアを自動的にマルチコア向けにコンパイルするツールが自動並列化コンパイラです。
前回の動画で紹介したMBP (Model Based Parallelizer) (https://www.esol.co.jp/embedded/product/embp_overview.html )はMathworks社のSimulinkモデルを入力とする自動並列化ツールです。C/C++言語プログラムなどからの自動並列化ツールとしては、Intel社のコンパイラ (https://www.xlsoft.com/jp/products/intel/oneapi/index.html)や早稲田大学笠原研究室のOSCAR (https://www.kasahara.cs.waseda.ac.jp/index.html.ja)などが知られています。
自動並列化は以下の手順を全自動で実行します。手順については第12回「マルチコアで動作するソフトウェアとは」も参考にしてください。
①
ソフトウェアの分割
下図の①のようにソフトウェアを分割します。図において、カッコ内の数字は分割したソフトウェアの実行時間で、並列化の際に負荷分散を考慮するときに使います。
②
依存関係の抽出
下図の②のように分割したソフトウェアの依存関係(データの読み書き順序などの理由で生じる処理の実行順序関係)を抽出します。
③
並列化コードを生成
分割したソフトウェアを、実行環境で並列に動かすことができるソフトウェア部品(タスクやスレッド)としてコードを生成し、必要に応じて部品(タスク、スレッド)間通信等を挿入してプログラムを生成します。下図の③は実行例で、塗りつぶした長方形が部品間通信です。
今年も猛暑ですね。私の住む名古屋も体温を超える気温が当たり前となってきております。
本コンソーシアムで実施しているアンケートでも、マルチコア利用はほぼ当たり前になって来ています。一方、マルチコアの使いこなしが出来ているかというと、まだまだという状況の様です。「マルチコアを使ってしばらく経つが、正直あまりよく分かっていないけど、今更基本は聞けない。。」、「分かっているつもりだが、正直自信はない。。」という方も少なくないのではないでしょうか。そんな皆様に少しでもお役に立てれば、ということで本コンソーシアムではYouTubeにEMCチャンネルで基本のお話を紹介してきましたが、今回もう少し詳しく、図なども踏まえて「マルチコアの基本」を紹介するブログ記事シリーズを始めました。このシリーズでは、マルチコア・メニーコアとは、マルチコアの種類、マルチコアと電力や性能の関係、シングルコア上のソフトウェアからの移行など、基本的な用語や考え方を説明しています。
例えば、第1回ではマルチコア・メニーコアとは何かについて説明しています。第10回、第11回では、下の図のような例題をもとにマルチコア上でのソフトウェア性能見積の基本と難しさについて紹介しています。第12回では同じ例を使ってシングルコア上のソフトウェアからの移行の基本について説明し、第13回ではそのようなソフトウェアの資産性や再利用性の確保、第14回ではマルチコアに移行するときのルールについて動画コンテンツを用いて紹介しています。
マルチコアに対してよく聞かれる質問です。第12回で書いたように、マルチタスクで記載されたプログラムはマルチコアでそのまま動作する可能性はありますが、そうでないプログラムはそのままでは一つのコアでしか動かないため、自動もしくは手動での並列化が必要です。
いずれの手段を取るにしても、これまで一つのコアで順番に動いていた処理が同時に動くようになるため、いくつかのルールを守らないと動作結果やふるまいが「そのまま」ではなくなる場合があります。
次の動画では、これらの観点から質問に答えています。
なお、すべての動画はEMCのYouTube動画ページからご覧いただけます。
●マルチコア適用ガイドのリニュアル版公開
●EMCブログ「はじめての並列化」も好評連載中
一般社団法人 組込みマルチコアコンソーシアム
〒104-0042 東京都中央区入船1丁目5-11 弘報ビル5階