結果の加算周りが非効率なためこの記事に載せたプログラムは遅いです。
この部分を修正した場合、AVX2無しで動かしたほうが高速です。
wrongwrong163377.hatenablog.com
記事中のAVX2命令を用いたプログラムはcmp命令を使っていないため低速な状態です。これを修正したものは以下。
wrongwrong163377.hatenablog.com
AVX2を使ってみました。ついでにAVX2の有無&OpenMPによる並列化の有無で比較してみました。
基本的な使い方
本題に入る前にAVX命令の基本的な使い方をまとめます。
作成環境
Visual Studio 2017のCmakeプロジェクトで作成しました。
includeするもの
immintrin.hをincludeすればOKです。一応このヘッダを入れておけばSIMD命令全般が動かせます。
このヘッダの内容は参考文献を御覧ください。
Cmakeのオプション指定
OpenMPが混ざっててアレですが、AVX2を使うならフラグに/arch:AVX2を追加します。こちらも詳しくは参考文献を御覧ください。
cmake_minimum_required (VERSION 3.8) find_package( OpenMP ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS} /O2 /arch:AVX2") add_executable (CMakeProject1 "CMakeProject1.cpp" "CMakeProject1.h")
基本的な処理の流れ
AVX2(に限らずSIMD命令全般)を使った処理の流れは以下の通りです。
- 変数に対して、load命令で配列から読み込むか、set命令で1つの値でクリアして初期化。
- 演算を行う。
- store命令で配列にデータを書き出す。
要するに、演算前に処理しやすいようにまとめる処理と処理したものを配列の形に戻す処理が挟まるということですね。
add、mul、subのような基礎演算とset、load、storeのようなメモリへの読み書きが一番大きいでしょうか。この辺を使えるようになれば、探せばほかの操作もできると思います。
ただ、命名規則や実装内容が結構不規則な点は非常に使いにくいです。ほぼ自分も覚えられていません。
作ったもの
円周率をシミュレートするプログラムを以下のバリエーションで作成しました。
- シングルスレッド・AVX2無し(StNs)
- マルチスレッド・AVX2無し(MtNs)
- シングルスレッド・AVX2無し(StWs)
- マルチスレッド・AVX2有り(MtWs)
関数の処理の流れ
要するに原点を中心にした(1, 0)から(0, 1)までの半径1の1/4の円と(1, 0)から(0, 1)を対角線に持つ正方形を用意し、正方形の範囲に乱数的に点を沢山打つと、面積の比から円周率が計算できるというアレです。
出力例
num = 10000000での出力結果の例です*4。
StNs : 0.695000 sec. pi = 3.141440 MtNs : 0.174000 sec. pi = 3.141440 StWs : 0.605000 sec. pi = 3.141740 MtWs : 0.166000 sec. pi = 3.141740
比較
実行環境
CPU | Core i7 6700 |
---|---|
メモリー | 16GB(2133C13) |
OS | Windows10 |
コンパイラ | VC++2017*5 |
ビルドオプション | /O2 /arch:AVX2 (x64 Release) |
実行結果
numを変化させながら()として計測した結果です*6。
X = 7 | StNs | MtNs | StWs | MtWs |
---|---|---|---|---|
実行時間 | 0.695 | 0.174 | 0.605 | 0.166 |
/StNs[%] | 100 | 25.0 | 87.1 | 23.9 |
X = 8 | StNs | MtNs | StWs | MtWs |
実行時間 | 6.221 | 1.681 | 5.836 | 1.648 |
/StNs[%] | 100 | 27.0 | 93.8 | 26.5 |
X = 9 | StNs | MtNs | StWs | MtWs |
実行時間 | 61.813 | 16.362 | 58.599 | 16.172 |
/StNs[%] | 100 | 26.5 | 94.8 | 26.2 |
参考文献
koturn様によるSIMD組み込み関数のまとめ。コンパイラ毎のコンパイルオプションや命令などが分かりやすくまとまっている。 | SIMDの組み込み関数のことはじめ - koturnの日記 |
Intelによるリファレンス。 | Intel Intrinsics Guide |
@tanakmura様によるAVX512命令を用いたシャッフルプログラムのサンプル。 | vshufps - Qiita |
@yohholy様によるx86SIMD Intrinsicとヘッダファイルの対応のまとめ。 | x86/SIMD Intrinsicとヘッダファイル対応表 - Qiita |
*1:IntelのメインストリームにAVX512が降りてくるのはCannon Lake以降になりそうです。
*2:外側の方が面積が狭いので、カウント加算処理が少なくなるため(特にブロックが減るマルチスレッドで)高速化できます。
*3:4倍を先にやってしまうとオーバーフローするので最後に回します。
*4:乱数周りの実装がマズく、シングルスレッドの実行時間が1秒以内に収まると乱数の種が一致してしまうため、piの計算結果が次のマルチスレッドの実行結果と被ります。
*5:バージョンが確認できず。2018/6/29時点で最新のものだったはず。
*6:X > 9ではunsigned longの最大値も超えるので実行しない。