4.2 SIMDによる高速化

4.2.1 SIMDプログラム

SIMDを用いることにより、計算時間の大部分を占める電磁界の更新を高速化することができます。
リスト4-2-1はEx成分を更新する関数の一番内側のkループをSSEで計算する部分です。
参考までに31行目以降に対応する非SIMDコードを載せます。
アドレスが連続でない4個の変数をベクトルレジスタに代入するには"_mm_set_ps"関数を使用し、 連続した4個の変数をベクトルレジスタに代入するには"_mm_loadu_ps"関数を使用します。
ここではアラインメントを考えていませんので関数名は"loadu/storeu"となります。 (アラインメントされているときは"load/store"です)
なお、X方向とY方向の差分はkによらないので12行目で"_mm_set1_ps"関数を使用し、 Z方向の差分はkによりますので13行目で"_mm_loadu_ps"関数を使用しています。
19-29行目が電界を更新する部分です。
AVXではベクトルレジスタが256ビットになり、 8個の変数をベクトルレジスタに代入します。 関数名は"_mm"が"_mm256"に変わります。


リスト4-2-1 SIMDプログラム(updateExの一部、SSEの部分)
     1		int64_t n = NA(i, j, 0);
     2		int64_t n1 = n - Nj;
     3		int64_t n2 = n - Nk;
     4		ks = ((Nz + 1) / 4) * 4;
     5		for (int k = 0; k < ks; k += 4) {
     6			int m0 = iEx[n + 0];
     7			int m1 = iEx[n + 1];
     8			int m2 = iEx[n + 2];
     9			int m3 = iEx[n + 3];
    10			__m128 c1 = _mm_set_ps(C1[m3], C1[m2], C1[m1], C1[m0]);
    11			__m128 c2 = _mm_set_ps(C2[m3], C2[m2], C2[m1], C2[m0]);
    12			__m128 yn = _mm_set1_ps ( RYn[j]);
    13			__m128 zn = _mm_loadu_ps(&RZn[k]);
    14			__m128 ex  = _mm_loadu_ps(&Ex[n]);
    15			__m128 hy1 = _mm_loadu_ps(&Hy[n]);
    16			__m128 hy2 = _mm_loadu_ps(&Hy[n2]);
    17			__m128 hz1 = _mm_loadu_ps(&Hz[n]);
    18			__m128 hz2 = _mm_loadu_ps(&Hz[n1]);
    19			_mm_storeu_ps(&Ex[n],
    20				_mm_add_ps(
    21					_mm_mul_ps(c1, ex),
    22					_mm_mul_ps(c2,
    23						_mm_sub_ps(
    24							_mm_mul_ps(yn, _mm_sub_ps(hz1, hz2)),
    25							_mm_mul_ps(zn, _mm_sub_ps(hy1, hy2))
    26						)
    27					)
    28				)
    29			);
    30			n += 4;
    31			n1 += 4;
    32			n2 += 4;
    33		}
    30	
    31		(参考:非SIMDコード)
    32		int64_t n = NA(i, j, 0);
    33		int64_t n1 = n - Nj;
    34		int64_t n2 = n - Nk;
    35		for (int k = 0; k <= Nz; k++) {
    36			int m = iEx[n];
    37			Ex[n] = C1[m] * Ex[n]
    38			      + C2[m] * (RYn[j] * (Hz[n] - Hz[n1])
    39			               - RZn[k] * (Hy[n] - Hy[n2]));
    40			n++;
    41			n1++;
    42			n2++;
    43		}

4.2.2 SIMDの計算時間

表4-2-1にSIMD関数を使用しないときとSSEまたはAVXを使用するときの計算時間を示します。
WindowsとLinuxの両方でSIMDにより1.4〜1.7倍速くなることがわかります。SSEとAVXの速さはほぼ同じです。

表4-2-1 SIMDの計算時間(()内はSIMDなしとの速度比)
ベンチマークNo.Windows (i7-4770K)Linux (Xeon E5-2670v2, icc17.0)
SIMDなしSSEAVXSIMDなしSSEAVX
129.1秒(1.0)20.7秒(1.41)21.4秒(1.36)30.6秒(1.0)17.7秒(1.73)18.0秒(1.70)
2233.5秒(1.0)159.1秒(1.47)162.1秒(1.44)242.1秒(1.0)153.9秒(1.57)152.1秒(1.59)

4.2.3 SIMDと並列化の関係

以上のようにSIMDを利用することによって一定の高速化は得られますが、 現在のC/C++コンパイラはSSEを使用する自動ベクトル化の機能を持っており、 特に並列計算と併用するときはSIMD演算を陽にプログラムすることによって必ずしも速くなるとは限りません。 以上の理由からSIMDは有意に速くなるときだけ使用してください。