ループアンローリングは、勝手に入ったり入らなかったりします。コンパイルオプション、#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 件のコメント:
コメントを投稿