ループアンローリングは、勝手に入ったり入らなかったりします。コンパイルオプション、#pragma を試してみましたが、試した限り効力はありませんでした。
1重ループでは、何をやってもループアンローリングしてしまいます。しかし、2重ループ(run_code1A)では、ループアンローリングは入りませんでした。
while (address_cnt < n) { int col = address_ptr[address_cnt]; float f = *(fp + col); __m128 B = _mm_load_ss(fp + address_ptr[address_cnt]); sum = _mm_add_ps(sum, B); ++address_cnt; }
自前のループアンローリングは、次のように記述しています。
}//Unrolling #define USE_MY_UNROLLING #ifdef USE_MY_UNROLLING while (address_cnt < n - 4) { __m128 B = _mm_load_ss(fp + address_ptr[address_cnt]); __m128 C = _mm_load_ss(fp + address_ptr[address_cnt + 1]); __m128 D = _mm_load_ss(fp + address_ptr[address_cnt + 2]); __m128 E = _mm_load_ss(fp + address_ptr[address_cnt + 3]); B = _mm_add_ps(B, C); D = _mm_add_ps(D, E); B = _mm_add_ps(B, D); sum = _mm_add_ps(sum, B); address_cnt += 4; } #endif while (address_cnt < n) { int col = address_ptr[address_cnt]; float f = *(fp + col); __m128 B = _mm_load_ss(fp + address_ptr[address_cnt]); sum = _mm_add_ps(sum, B); ++address_cnt; }
ベクトル化手法を取らなくても、上の記述だけで、Eigenより速くなりました。(
Eigenは、SparseSpMVについて明示的アンローリングは行っていません。)
(instance19:マルチスレッド数4、FirstOrderIteration10万回 SpMVあたりのデータ)
■Eigen :3.96sec
■上記コード(ループアンローリングなし):3.42sec
■ (ループアンローリングあり):2.996sec
これをマルチスレッド化するには、OpenMPの ScheduleをDynamicにして、さらにchunk数を指定すると上手く動作しました。Eigenの記述をそのまま使いました。 以上、ベクトル化前でも、ループアンローリング記述を追加するだけで、10-20%程度は、速度アップを期待できます。また、下のように2重ループで組んでも、データ的には、連続的に一重ループとして記述することが、重要なようです。キャッシュに収まり易いこともありますが、生成されるコードもシンプルになります。今回のようなクリティカルな関数では、一つのアセンブリコードの増減が速度に直結します。
void run_code1A(row_relay_struct* rrs, int rows, unsigned short* address_ptr, unsigned short* coeff_address_ptr, __m128* ifptr, float* ofptr, float* coeff_ptr) { /*LUT __m128 rrs rows row_relay_struct rows one_address_ptr unsigned short k address_ptr unsigned short n bits_ptr unsigned char n coff_address_ptr unsigned short m coff_ptr float m */ int threads = Eigen::nbThreads(); #pragma omp parallel for schedule(dynamic,(rows+threads*4-1)/(threads*4)) num_threads(threads) for (auto row = 0;row < rows;++row) { __m128 sum = { 0,0,0,0 }; int n = rrs[row].n; //assert(one_address_ptr + n == address_ptr); int address_cnt = rrs[row].address_cnt; if (row) { assert(rrs[row - 1].n == address_cnt); } float* fp = reinterpret_cast(ifptr); //Unrolling #define USE_MY_UNROLLING #ifdef USE_MY_UNROLLING while (address_cnt < n - 4) { __m128 B = _mm_load_ss(fp + address_ptr[address_cnt]); __m128 C = _mm_load_ss(fp + address_ptr[address_cnt + 1]); __m128 D = _mm_load_ss(fp + address_ptr[address_cnt + 2]); __m128 E = _mm_load_ss(fp + address_ptr[address_cnt + 3]); B = _mm_add_ps(B, C); D = _mm_add_ps(D, E); B = _mm_add_ps(B, D); sum = _mm_add_ps(sum, B); address_cnt += 4; } #endif while (address_cnt < n) { int col = address_ptr[address_cnt]; float f = *(fp + col); __m128 B = _mm_load_ss(fp + address_ptr[address_cnt]); sum = _mm_add_ps(sum, B); ++address_cnt; } n = rrs[row].m; address_cnt = rrs[row].coff_cnt; if (row) { assert(rrs[row - 1].m == address_cnt); } while (address_cnt < n) { unsigned short col = coeff_address_ptr[address_cnt]; float* fp = reinterpret_cast (ifptr); __m128 c = _mm_load_ss(fp + col); __m128 d = _mm_load_ss(coeff_ptr + address_cnt); c = _mm_mul_ss(c, d); sum = _mm_add_ps(sum, c); ++address_cnt; } _mm_store_ss(ofptr + row, sum); } }
まとめ 1)VisualStudioは、ループアンローリングは、制御できない 2)ループアンローリング効果は、10%-20%程度 3)OpenMp for threadingは、staticよりもchunk付きdynamic scheduling が速い 4)2重ループでもデータは、シーケンシャルで1重ループに。
0 件のコメント:
コメントを投稿