SIMDという言葉自体はよく聞いたことある(コンピュータ関係のお仕事をされている方や、PC好きな方は特に)のですが、活用しているかというと全くしていません。多分、ちょっとでもパフォーマンスアップしたら良いなぁとコンパイラオプションにSIMD有効化を指定するぐらいではないでしょうか。
(そもそもSIMDとはなんぞや、というのはWikipediaを参照ください)
ここではSIMDについての覚書を残しておきます。
SIMDとは
何度かBlogでも書いているVNCツールでも、画像処理部分を高速化する為に使いました。
SIMDというのは、例えばループ等で何度も同じ処理を行うものをまとめて1命令で実行することで高速化する仕組みです。例えば次のようなプログラムがあったとします。
void addArray(int values0[], int values1[], int result[],int length) { for(int i=0; i<length; i++) { result[i] = values0[i] + values1[i]; } }
配列values0とvalues1の値を要素ごとに加算して配列resultに格納するだけのシンプルなものです。加算処理は指定されたlength回繰り替えされます。今度はこれをSIMD(SSE2命令)で置き換えてみます。
void addArraySSE(int values0[], int values1[], int result[],int length) { for(int i=0; i<length; i+=4) { //load from value0,value1 __m128i val0 = _mm_load_si128((__m128i *)&values0[i]); __m128i val1 = _mm_load_si128((__m128i *)&values1[i]); //add value0,value1 value0 = _mm_add_epi32(val0, val1); //store result _mm_stream_si128((__m128i *)result[i], val0); } }
処理内容は変わりませんが、今度は_mm_load_si128を使って128bit分を一気に読み込んでいます。int型が32bitなので4要素分をまとめて読み込んだことになります。その後、_mm_add_epi32で先ほど読み込んだvalue0,value1を4要素まとめて加算します(結果はval0変数へ格納)。最後に計算結果を_mm_stream_si128でresult変数へ格納しています。
理屈の上では、4回分の処理をまとめて1回で行うので4倍早くなるはずです。
(もしchar型(8bit)の配列だった場合、16回分をまとめて行えるので16倍)
SIMDを使うところ
もちろん高速化を図りたい、というのが前提だと思います。現行処理のうち時間を食っている部分を割り出し、かつその部分がSIMD向きの処理である場合には大いに威力を発揮します。
SIMD向きの処理というのは、例でも挙げましたがループでぐるぐる同じ処理をまわしているような部分です。そして扱うデータがメモリ上で連続して配置されている必要があります。例ではデータを配列で扱っていたので連続しており問題ありませんでしたが、仮にデータがバラけて配置されている場合、そのままではSIMDで高速化を図ることができません。その際は(もしSIMDを用いるとするなら)SIMDで扱いやすいようデータの保持の仕方を変更する必要があります。
並列化
SIMDのように同じ処理をいっぺんに行うようなものを並列化プログラムというらしいのですが、並列化とはなんでしょう。最近のCPUには複数コアがあって同時に演算していますが何が違うのでしょう。
勉強中なので誤りがあるかもしれない、と前置きしつつ。
マルチコアCPUは、そのまんまですがCPUコアが複数搭載されています。それぞれのCPUコアは独立したプログラムカウンタ(PC)を持ち、独立して動作します。ある瞬間、CPUコアその1はyoutube動画の再生処理に励み、CPUコアその2は山盛りのデジカメ画像をリサイズするような処理に励んでいる、といったように全く異なったプログラムコードを実行しています。
グラフィックボード搭載GPUの場合、やっぱりコアが複数搭載されています。違うのは、プログラムカウンタは全てのコアで共有していることです。つまり、それぞれのコアで独立したコードを実行することが出来ません。if文のような命令分岐する際も、全てのコアが同じように分岐します。何処かのコアはif分岐のA処理へ、他はB処理へといったことは不可です。
で、あくまで私の所感ですが、並列化プログラミングとは如何に命令分岐しないようなコードを書くか、ということに集約されるのではないかと思います。
見慣れない書き方ではじめは戸惑うかもしれません。
(マイコン等でアセンブラ使った経験のある方は理解しやすいかもしれません。レジスタにロードして演算して、メモリにストアして~とかそんな感じです)
コンパイラによってはプログラム処理を判断してSIMDを用いるものもあるのかもしれませんが、
で、実際のところSIMDを活用しているかというと、まず使っていません。今までそんなに時間のかかる処理(プログラム)を書いたことなかったというのと、仮に時間がかかったとしても遅いな~、まぁしかたないかで済ませていました。勝手に進むし、多少のことは我慢しとこうという感じです。