1. ホーム
  2. performance

[解決済み] なぜベクトル化、ループよりも一般的に速いのですか?

2022-02-15 10:40:20

質問

なぜ、演算を行うハードウェアの最下層や、関係する一般的な基礎演算(つまり、コードを実行する際のすべてのプログラミング言語の実際の実装に共通するもの)において、ベクトル化は通常、ループ処理よりも劇的に速いのでしょうか。

ループの時にコンピュータがやっていて、ベクトル化の時にはやらないこと(プログラマーが書いたことではなく、コンピュータが実際に行う計算のことです)、あるいは違うことをやっているのでしょうか?

なぜ、これほどまでに差が出るのか、自分でも納得がいきません。 ベクトル化されたコードは、どこかでループのオーバーヘッドを削減しているのだろうと納得できるかもしれませんが、それでもコンピュータは同じ数の演算を実行しなければなりませんよね? 例えば、サイズNのベクトルとスカラーを掛け合わせる場合、どちらにしてもN回の掛け算が必要ですよね?

どのように解決するのか?

ベクタライズ(通常使われる用語)とは、SIMD(単一命令、複数データ)演算のことです。

これは要するに、1つの命令で複数のオペランドに対して同じ演算を並列に行うことを意味します。例えば、大きさNのベクトルとスカラを掛け合わせる場合、同時に演算できるその大きさのオペランドの数をMとしよう。そうすると、(純粋なスカラー演算では)N回の演算を行う必要があるところ、実行すべき命令数はおよそN/Mとなる。

例えば、Intelの現在のAVX 2命令セットでは、256ビットのレジスタを使用しています。このレジスタは、64ビット×4本、または32ビット×8本のオペランドを保持(演算)するために使用される。

つまり、32ビットの単精度の実数を扱うと仮定すると、1つの命令で一度に8つの演算(掛け算)ができることになり、(少なくとも理論上は)N/8の掛け算命令だけでN個の掛け算を終わらせることができる。少なくとも理論上は、1命令ずつ実行した場合の約8倍の速度で演算を終了できるはずです。

もちろん、正確なメリットは、1命令あたり何個のオペランドをサポートするかによって異なります。Intelの最初の試みは64ビットのレジスタしかサポートしていなかったので、一度に8つの項目を操作するには、それらの項目は1つにつき8ビットでなければなりませんでした。現在では256ビットレジスタをサポートしており、512ビットのサポートも発表している(一部のハイエンドプロセッサには搭載されているかもしれないが、通常のコンシューマ向けプロセッサには少なくともまだ搭載されていない)。この機能をうまく利用することも、控えめに言っても自明なことではありません。N個のオペランドを適切なタイミングで適切な場所に配置できるように命令をスケジューリングすることは、必ずしも簡単な作業ではありません(まったくもって)。

このような観点から、Cray 1(現在は旧式)はまさにこの方法で多くの速度を獲得してきました。ベクトルユニットは、1つにつき64ビットの64本のレジスタで動作するため、1クロックあたり64回の倍精度演算が可能でした。最適にベクトル化されたコードでは、そのクロック速度だけから想像するよりもはるかに現在のCPUの速度に近かったのだ。しかし、その利点をフルに生かすことは必ずしも容易ではありませんでした(現在もそうです)。

ただし、ベクトル化というのは覚えておいてください。 ではない CPUが並列に処理を行う唯一の方法です。1つのCPU(またはCPUのシングルコア)が一度に複数の命令を実行する、命令レベルの並列処理も可能です。最近のCPUは、1クロックあたり最大4命令まで実行できるハードウェアを搭載しているものがほとんどです。 1 ロード、ストア、ALUが混在している場合。 メモリがボトルネックになっていない場合、うまく調整されたループでは、平均して1クロックあたり2命令近く、あるいはそれ以上実行することができます。

そしてもちろん、マルチスレッド、つまり(少なくとも論理的には)別々のプロセッサ/コアで複数の命令のストリームを実行することができます。

つまり、最新のCPUは、例えば4つのコアを持ち、それぞれが1クロックあたり2つのベクトル乗算を実行でき、それらの命令はそれぞれ8つのオペランドに対して演算することができます。つまり、少なくとも理論上は、1クロックあたり4×2×8=64回の演算を実行できることになります。

命令によっては、スループットが良いものと悪いものがあります。 例えば、FP adds のスループットは Skylake 以前の Intel では FMA や multiply よりも低くなっています(1クロックあたり 2 ベクターではなく 1 ベクター)。 しかし、ANDやXORなどのブーリアンロジックは1クロックあたり3ベクタのスループットを持っており、AND/XOR/OR実行ユニットを作るには多くのトランジスタが必要ないため、CPUはそれらを複製している。 高スループットの命令を使う場合は、特定の実行ユニットでのボトルネックよりも、パイプラインの総幅(コアのアウトオブオーダー部分にデコードして発行するフロントエンド)でのボトルネックが一般的である。


  1. しかし、時間が経つとCPUはより多くのリソースを利用できるようになる傾向があるので、この数値は上昇します。