C++
AVX
AVX2

LJの力計算のSIMD化ステップ・バイ・ステップ その4

More than 1 year has passed since last update.

はじめに

前回までで4倍展開したループのSIMD化が完了した。いよいよ実測である。

コードは
https://github.com/kaityo256/lj_simdstep
においてある。

SIMD化完了

4倍展開したSIMD化が完了した。基本的に4倍展開されたループのSIMD化はほぼ自明だが、少しだけ非自明なのは、4倍展開して得られた4つの相対距離を一つのYMMレジスタにまとめ、力の計算を4ペア同時に行ってから、またバラす作業。まずまとめるところ。

    v4df vr2_13 = _mm256_unpacklo_pd(vr2_1, vr2_3);
    v4df vr2_24 = _mm256_unpacklo_pd(vr2_2, vr2_4);
    v4df vr2 = _mm256_shuffle_pd(vr2_13, vr2_24, 12);
    v4df vr6 = vr2 * vr2 * vr2;
    v4df vdf = (vc24 * vr6 - vc48) / (vr6 * vr6 * vr2);

    v4df mask = vcl2 - vr2;
    vdf = _mm256_blendv_pd(vdf, vzero, mask);

vr2_1からvr2_4までには、(r2_1, r2_1, r2_1, 0)みたいなデータが入っている。ここから(r2_1, r2_2, r2_3, r2_4)を作るのに、unpack二回とshuffleを使っている。その後は普通に計算している。ただし相互作用範囲外のペアの処理が必要となるため、blendv_pdでマスク処理をしている。

次にバラすところ。

    v4df vdf_1 = _mm256_permute4x64_pd(vdf, 0);
    v4df vdf_2 = _mm256_permute4x64_pd(vdf, 85);
    v4df vdf_3 = _mm256_permute4x64_pd(vdf, 170);
    v4df vdf_4 = _mm256_permute4x64_pd(vdf, 255);

計算された力(df1,df2,df3,df4)から、(df1,df1,df1,df1)〜(df4,df4,df4,df4)を作っているこうすると力積ベクトルの計算がスムーズなので。非自明なところはこれくらいだと思う。

結果

計算手法 実行時間[sec] 備考
pair 8.176446 ペアごとにループをまわしたもの
pair_swp 6.995374 上記をソフトウェアパイプライニングしたもの
pair_swp_intrin 4.276301 上記を4倍展開してSIMD化したもの
sorted_intrin 3.882854 i粒子でソートしたものをSIMD化したもの

・・・ソートバージョンのSIMD化に負けた ○| ̄|_

それなりに加速したものの、ソートバージョンに低密度でも負けたので、ループ長が短いところで「ゴミがほとんど生じない」メリットは、メモリアクセスが増えるデメリットに負けたようだ。

調べてみると、ソートしてソフトウェアパイプライニングしたのの(SIMD化前)のIPCが2.12なのに対し、ソートしないでソフトウェアパイプライニングしたもの(SIMD化前)のIPCが1.99と若干低く、かつ命令数が増えてしまっているのが敗因のようだ。

残念ながら、今回の方針では早くならなかった。参考にならないだろうが、コードは

https://github.com/kaityo256/lj_simdstep/tree/master/step4

においておく。

次回に続く