LJの力計算のSIMD化ステップ・バイ・ステップ その6
LJの力計算のSIMD化ステップ・バイ・ステップ その6
はじめに
そうとうがんばってSIMD化して、もう速くならないだろうと思ってたらkohnakagawaさんから「もう少し高速化しました」というプルリクが来て慌てた話。
コードは https://github.com/kaityo256/lj_simdstep においてある。
どこを直したか
一度に256ビット取ってくる命令を使うため、配列をパディングして、(x,y,z,0)という並びにしていた。それを4要素のベクトルとしてロードして、相対座標ベクトルを作るのだが、ループを4倍展開しているので、
\[(dx_1, dy_1, dz_1, 0)\\ (dx_2, dy_2, dz_2, 0)\\ (dx_3, dy_3, dz_3, 0)\\ (dx_4, dy_4, dz_4, 0)\]という四本のベクトルができる。これを、それぞれ自乗してからシャッフルして足すことで、相対距離を作っていた。
\[(dr^2_1, dr^2_1, dr^2_1,0) \\ (dr^2_2, dr^2_2, dr^2_2,0) \\ (dr^2_3, dr^2_3, dr^2_3,0) \\ (dr^2_4, dr^2_4, dr^2_4,0) \\\]で、これを転置して、
\[v_r = (dr^2_1, dr^2_2, dr^2_3, dr^4)\]を作ってから力の計算を4並列でやっていた。しかし、どうせ最後に転置するなら、最初に転置した方が計算量が減る。つまり、
\[(dx_1, dy_1, dz_1, 0)\\ (dx_2, dy_2, dz_2, 0)\\ (dx_3, dy_3, dz_3, 0)\\ (dx_4, dy_4, dz_4, 0)\]を転置して、
\[v_x = (dx_1, dx_2, dx_3, dx_4)\\ v_y = (dy_1, dy_2, dy_3, dy_4)\\ v_z = (dz_1, dz_2, dz_3, dy_4)\\ v_0 = (0, 0, 0, 0)\]を得たら、あとは
\[v_r = v_x^2+v_y^2+v_z^2\]| でできるじゃん、というもの。言われてみればそれはそうだ。なんで気が付かなかったんだろ○ |  ̄ | _ |
結果
というわけで上記を実装したものをこれまでと同条件で測定。
| 計算手法 | 実行時間[sec] | 備考 |
|---|---|---|
| pair | 8.201647 | ペアごとにループをまわしたもの |
| pair_swp | 7.094673 | 上記をSWPしたもの |
| pair_swp_intrin | 4.282396 | 上記を4倍展開してSIMD化したもの |
| sorted | 7.040513 | i粒子でソートしたもの |
| sorted_intrin | 3.889211 | i粒子でソートしたものをSIMD化したもの |
| sorted_swp | 5.806741 | i粒子でソートしたものをSWPしたもの |
| sorted_swp_intrin | 3.272044 | i粒子でソートしたものをSWPしたものをSIMD化したもの |
| sorted_swp_intrin_mat_transp | 2.980598 | 上記の転置順序を変えたもの |
10%弱高速化されましたな。
コードは以下に。
https://github.com/kaityo256/lj_simdstep/tree/master/step6
まとめ
もともと、4倍展開する前にSIMD化してたものを4倍展開してから馬鹿SIMD化したので、いろいろ計算した後、最後に転置する形しか考えつかなかった。でも、プルリクもらってよく考えたら、先に転置しちゃった方が計算量少ないから速い。うーん、まだまだ修行が足りませんな。
この様子じゃ、データ構造とか工夫したり、ループのほどきかた工夫したりしたら、まだまだ高速化の余地がありそうですね。
オレはようやくのぼりはじめたばかりだからな このはてしなく遠いSIMD坂をよ・・・
こんどこそおしまい?
A Robot’s Sigh