[解決済み] なぜベクトル化、ループよりも一般的に速いのですか?
質問
なぜ、演算を行うハードウェアの最下層や、関係する一般的な基礎演算(つまり、コードを実行する際のすべてのプログラミング言語の実際の実装に共通するもの)において、ベクトル化は通常、ループ処理よりも劇的に速いのでしょうか。
ループの時にコンピュータがやっていて、ベクトル化の時にはやらないこと(プログラマーが書いたことではなく、コンピュータが実際に行う計算のことです)、あるいは違うことをやっているのでしょうか?
なぜ、これほどまでに差が出るのか、自分でも納得がいきません。 ベクトル化されたコードは、どこかでループのオーバーヘッドを削減しているのだろうと納得できるかもしれませんが、それでもコンピュータは同じ数の演算を実行しなければなりませんよね? 例えば、サイズ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はそれらを複製している。 高スループットの命令を使う場合は、特定の実行ユニットでのボトルネックよりも、パイプラインの総幅(コアのアウトオブオーダー部分にデコードして発行するフロントエンド)でのボトルネックが一般的である。
- しかし、時間が経つとCPUはより多くのリソースを利用できるようになる傾向があるので、この数値は上昇します。
関連
-
[解決済み] spark.sql.shuffle.partitionsとspark.default.parallelismの違いは何ですか?
-
[解決済み] SQLiteのINSERT/per-secondのパフォーマンスを向上させる
-
[解決済み] B "の印刷が "#"の印刷より劇的に遅いのはなぜですか?
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み] 要素ごとの加算は、結合ループよりも分離ループの方がはるかに高速なのはなぜですか?
-
[解決済み] <は<=より速いのか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] なぜJavaでは2 * (i * i)の方が2 * i * iより速いのですか?
-
[解決済み] なぜ[]はlist()よりも速いのですか?
-
[解決済み] Scalaのlazy valの(隠れた)代償は何なのか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 実行時間(高速化)の計算方法
-
[解決済み] HadoopのMapreduceジョブでJVMを再利用する。
-
[解決済み] 最後の手段としてのパフォーマンス最適化戦略【終了しました
-
[解決済み】再帰はループより速いことがあるのか?
-
[解決済み】再帰と反復のどちらを選ぶ?
-
[解決済み】長さnのソートされていない配列の中でk番目に大きい要素をO(n)で見つけるにはどうすればよいですか?)
-
[解決済み】GHCコアの読み込み
-
[解決済み] Apache Spark: map vs mapPartitions?
-
[解決済み】2次元の点がポリゴン内にあるかどうかを判断するにはどうしたらいいですか?
-
[解決済み] リストの各要素に数値を乗じるには?