【シムディーと読もう!】SIMDによる高速化の実装方法3選
30年以上のプログラミング歴の半分は高速化をしてきました、ムンペイです。
複数のデータを同時に処理する並列化は、高速化の最重要テクニックの1つです。そのうち、1つのCPUコアの中で並列化を実現する命令レベル並列化について、SIMD演算を適用する方法について、私の経験をもとにしてご紹介します。
SIMDはシムディーと読もう!
SIMDとは、Single Instruction, Multiple Dataの略で、1つの命令で複数のデータを処理する方式を指す言葉ですが。読み方は、日本では「シムド」もよく聞きますが、英語話者では「シムディー」のようです。これからは格好つけてシムディーと呼びましょう!
64bit CPUなどと言う言葉を聞いたことがあると思います。これは、CPUが処理するデータ長が64bit単位であるということを表しています。40年前は8bitが主流で、その後半導体製造技術が高まって搭載できるトランジスタが増えるのに伴い64bitまで増えてきました。
ビット数を増やしたい理由は2つあります。
まず、1クロックサイクルで計算できる桁数が増えることで、計算に必要なサイクル数が減り、高速になることです。もう1つは、1回のメモリアクセスで運べるデータが増えて、メモリアクセスに必要なサイクル数が減り、高速になることです。
しかし、計算できる桁数は64bitで十分です。10bitで扱える値が1024倍になるので、大体10進数3桁分増えます。64bitだと10の18乗以上、100京以上ってことですね。これ以上の桁数を必要とするのは天文学とか量子物理学とかに限られるでしょう。つまり、128bitパソコンは登場しないわけです。(ちなみに、64bitの前は32bitでしたが、9桁=10億程度なので64bitにするモチベはわかりますね)
一方、メモリアクセスは、半導体製造上許す限りビット数を増やせば増やすだけ高速になります。
ということで、現代のCPUでは、メモリアクセスは256bitで、計算は64bitという、アンバランスなことになっていたりします。
ならば、256bitを読み取って、64bitの計算を4個同時に行ったらどうだい?というのがSIMDです。(前フリが長かったですね。。)
一般的なWindowsマシンで使われている、x86アーキテクチャのCPU(IntelやAMDのCPU)であればSSEという命令セットの中にSIMD演算命令が含まれています。徐々に命令が追加されていったので、SSE、SSE2、SSE4.1とか色々種類がありますが、新しいCPUを使う分には気にしなくていいでしょう。
1.最適化されたライブラリを使う
SIMD演算を使うには、いくつか方法がありますが、まず1番簡便で効率的なのは、SIMDを使って最適化されたライブラリを使うことです。熟練プログラマーによる実装の恩恵を受けられるでしょう。
Pythonであればnumpyやpandasが代表的だと思います。
C/C++では、画像処理であればOpenCVや、フーリエ変換のfftwなど、それこそ無数にあります。
2.コンパイラに任せる
自分で作る場合には、まずコンパイラに自動的にSSE命令を使ってもらう方法を試します。コンパイラオプションで最適化レベルを高めに設定すると使ってくれます。
どのデータが並列化できるかはコンパイラが自動的に検出するわけですが、人間のソースコードの書き方を工夫するとより良く検出できる場合があります。たとえば、同じインデックス変数を共有できるループであっても、異なる計算であれば敢えて2つに分けて書くと良いかもしれません。コンパイラに対して、各ループは同じ演算を繰り返すということを示すわけです。
3.intrinsicを使う
自動SIMDでは満足にSIMD化されない場合は、プログラマーが直接書きます。と言っても、普通はコンパイラがSIMD命令を関数呼び出しのような形で提供しているintrinsic(イントリンシック)命令というものを使います。
自分が指示した命令が、バイナリに直接出てくるのはちょっとしたドキドキや興奮がありますよ。
今回は短いですが
本当は、MIMDのことを書こうと思っていたのですが、前提としてSIMDのことを書いているうちに長くなってしまいましたので分けることにしました。そのようなわけで、MIMDの話をまたの機会に。
これにて御免!