contributed by <ierosodin
>
jackyhobingo
hardware prefetching
software prefetching
Data Structures and Corresponding Prefetching
structure | code | pattern | sizeof a | index | SW | HW |
---|---|---|---|---|---|---|
Array | a[i] | stream | < cache block | direct | a[i+D] | stream |
Array | a[i], a[i][const] | stride | > cache block | direct | a[i+D][j] | stride, GHB |
Array | a[b[i]] | irregular | < cache block | indirect | a[b[i+D]] | CDP, markov… |
RDS | a = a -> next | stride, irregular | indirect | a -> next ->next… | CDP, markov… | |
Hash | b[f(i)] -> a | irregular | indirect | Group prefetching… | helper threads, pre-computation |
分類 prefetch 的精確度
|Classification|Accuracy|Timeliness|
|:–-:|:–-:|:–-:|:–-:|
|Timely|accurate|best prefetching|
|Late|accurate|request before prefetch|
|Early|accurate|request affter prefetch|
|Redundant dc|accurate|the same cache block is in data cache|
|Redundant mshr|accurate|the same cache block is in MSHR|
|Incorrect|inaccurate|block is never used|
Software Prefetch Distance
Direct and Indirect Memory Indexing
Benefits of Software Prefetching
Negative Impacts of Software Prefetching
Synergistic Effects
Antagonistic Effects
改成分別產生 naive_transpose
sse_transpose
sse_prefetch_transpose
執行檔,並用 -D 來辨別實作的方法。
修改 impl.c ,用 #ifdef
來區別不同的 transpose 方法,這樣在 main.c 中,同樣是呼叫 transpose() ,會因為不同的 define ,而呼叫不一樣的實作函式。
SEE 儲存變數的 type 是 __m128i ,因此可以存 4 的整數(32 bits)。
__m128i _mm_loadu_si128 (__m128i *p);
I0 | 01 | 02 | 03 | 04 |
---|---|---|---|---|
I1 | 11 | 12 | 13 | 14 |
I2 | 21 | 22 | 23 | 24 |
I3 | 31 | 32 | 33 | 34 |
__m128i _mm_unpacklo_epi32 (__m128i a, __m128i b);
__m128i _mm_unpackhi_epi32 (__m128i a, __m128i b);
D0 = _mm_unpacklo_epi32(I0, I1) | 01 | 11 | 02 | 12 |
---|---|---|---|---|
D1 = _mm_unpacklo_epi32(I2, I3) | 21 | 31 | 22 | 32 |
D2 = _mm_unpackhi_epi32(I0, I1) | 03 | 13 | 04 | 14 |
D3 = _mm_unpackhi_epi32(I2, I3) | 23 | 33 | 24 | 34 |
__m128i _mm_unpacklo_epi64 (__m128i a, __m128i b);
__m128i _mm_unpackhi_epi64 (__m128i a, __m128i b);
I0 = _mm_unpacklo_epi64(D0, D1) | 01 | 11 | 21 | 31 |
---|---|---|---|---|
I1 = _mm_unpackhi_epi64(D0, D1) | 02 | 02 | 22 | 32 |
I2 = _mm_unpacklo_epi64(D2, D3) | 03 | 13 | 23 | 33 |
I3 = _mm_unpackhi_epi64(D2, D3) | 04 | 14 | 24 | 34 |
__m128i _mm_storeu_si128 (__m128i *p, __m128i a);
I0 | 01 | 11 | 21 | 31 |
---|---|---|---|---|
I1 | 02 | 02 | 22 | 32 |
I2 | 03 | 13 | 23 | 33 |
I3 | 04 | 14 | 24 | 34 |
從 SSE 實作 transpose 的方法可以發現,矩陣的邊長必須是 4 的倍數!ierosodin
以 gnuplot ,畫出三種方法矩陣邊長對時間的折線圖:
可以發現使用 prefetch 明顯的提升了轉置矩陣的速度,但 naive 與 sse 兩者的效能卻很難比較,並發現 sse 矩陣邊長對時間的震盪很明顯且似乎有規律!拉出來分析:
這是從邊長 1600 開始,每次加 4 所得到的結果,可以發現,邊長為 8 的倍數的表現明顯較好
是 cache 的行為造成的?思考如何解釋這個現象…ierosodin
有關 SIMD 語法的使用,可以參考The Intel Intrinsics Guide
for (int x = 0; x < w; x += 8) {
for (int y = 0; y < h; y += 8) {
__m256 I0 = _mm256_loadu_ps((src + (y + 0) * w + x));
__m256 I1 = _mm256_loadu_ps((src + (y + 1) * w + x));
__m256 I2 = _mm256_loadu_ps((src + (y + 2) * w + x));
__m256 I3 = _mm256_loadu_ps((src + (y + 3) * w + x));
__m256 I4 = _mm256_loadu_ps((src + (y + 4) * w + x));
__m256 I5 = _mm256_loadu_ps((src + (y + 5) * w + x));
__m256 I6 = _mm256_loadu_ps((src + (y + 6) * w + x));
__m256 I7 = _mm256_loadu_ps((src + (y + 7) * w + x));
__m256 T0 = _mm256_unpacklo_ps(I0, I1);
__m256 T1 = _mm256_unpacklo_ps(I2, I3);
__m256 T2 = _mm256_unpackhi_ps(I0, I1);
__m256 T3 = _mm256_unpackhi_ps(I2, I3);
__m256 T4 = _mm256_unpacklo_ps(I4, I5);
__m256 T5 = _mm256_unpacklo_ps(I6, I7);
__m256 T6 = _mm256_unpackhi_ps(I4, I5);
__m256 T7 = _mm256_unpackhi_ps(I6, I7);
__m256d D0 = _mm256_castps_pd(T0);
__m256d D1 = _mm256_castps_pd(T1);
__m256d D2 = _mm256_castps_pd(T2);
__m256d D3 = _mm256_castps_pd(T3);
__m256d D4 = _mm256_castps_pd(T4);
__m256d D5 = _mm256_castps_pd(T5);
__m256d D6 = _mm256_castps_pd(T6);
__m256d D7 = _mm256_castps_pd(T7);
__m256d J0 = _mm256_unpacklo_pd(D0, D1);
__m256d J1 = _mm256_unpackhi_pd(D0, D1);
__m256d J2 = _mm256_unpacklo_pd(D2, D3);
__m256d J3 = _mm256_unpackhi_pd(D2, D3);
__m256d J4 = _mm256_unpacklo_pd(D4, D5);
__m256d J5 = _mm256_unpackhi_pd(D4, D5);
__m256d J6 = _mm256_unpacklo_pd(D6, D7);
__m256d J7 = _mm256_unpackhi_pd(D6, D7);
D0 = _mm256_permute2f128_pd(J0, J4, 0x20);
D1 = _mm256_permute2f128_pd(J1, J5, 0x20);
D2 = _mm256_permute2f128_pd(J2, J6, 0x20);
D3 = _mm256_permute2f128_pd(J3, J7, 0x20);
D4 = _mm256_permute2f128_pd(J0, J4, 0x31);
D5 = _mm256_permute2f128_pd(J1, J5, 0x31);
D6 = _mm256_permute2f128_pd(J2, J6, 0x31);
D7 = _mm256_permute2f128_pd(J3, J7, 0x31);
I0 = _mm256_castpd_ps(D0);
I1 = _mm256_castpd_ps(D1);
I2 = _mm256_castpd_ps(D2);
I3 = _mm256_castpd_ps(D3);
I4 = _mm256_castpd_ps(D4);
I5 = _mm256_castpd_ps(D5);
I6 = _mm256_castpd_ps(D6);
I7 = _mm256_castpd_ps(D7);
_mm256_store_ps((dst + ((x + 0) * h) + y), I0);
_mm256_store_ps((dst + ((x + 1) * h) + y), I1);
_mm256_store_ps((dst + ((x + 2) * h) + y), I2);
_mm256_store_ps((dst + ((x + 3) * h) + y), I3);
_mm256_store_ps((dst + ((x + 4) * h) + y), I4);
_mm256_store_ps((dst + ((x + 5) * h) + y), I5);
_mm256_store_ps((dst + ((x + 6) * h) + y), I6);
_mm256_store_ps((dst + ((x + 7) * h) + y), I7);
}
}
雖然成功的將 8*8 矩陣轉置,但當矩陣邊長增加時,卻出現了 Segmentation fault (core dumped) !問題出在 avx 僅有 15 個 register (ymm0~ymm14), 然而 code 中卻大量宣告 __m256 。
register 不是免費的阿!!!ierosodin
利用 _mm_256_shuffle_ps 代替 _mm256_unpacklo_pd ,這樣可以免去資料型態的轉換
for (int x = 0; x < w; x += 8) {
for (int y = 0; y < h; y += 8) {
__m256 I0 = _mm256_loadu_ps((src + (y + 0) * w + x));
__m256 I1 = _mm256_loadu_ps((src + (y + 1) * w + x));
__m256 I2 = _mm256_loadu_ps((src + (y + 2) * w + x));
__m256 I3 = _mm256_loadu_ps((src + (y + 3) * w + x));
__m256 I4 = _mm256_loadu_ps((src + (y + 4) * w + x));
__m256 I5 = _mm256_loadu_ps((src + (y + 5) * w + x));
__m256 I6 = _mm256_loadu_ps((src + (y + 6) * w + x));
__m256 I7 = _mm256_loadu_ps((src + (y + 7) * w + x));
__m256 T0 = _mm256_unpacklo_ps(I0, I1);
__m256 T1 = _mm256_unpacklo_ps(I2, I3);
__m256 T2 = _mm256_unpackhi_ps(I0, I1);
__m256 T3 = _mm256_unpackhi_ps(I2, I3);
__m256 T4 = _mm256_unpacklo_ps(I4, I5);
__m256 T5 = _mm256_unpacklo_ps(I6, I7);
__m256 T6 = _mm256_unpackhi_ps(I4, I5);
__m256 T7 = _mm256_unpackhi_ps(I6, I7);
I0 = _mm256_shuffle_ps(T0, T1, _MM_SHUFFLE(1,0,1,0));
I1 = _mm256_shuffle_ps(T0, T1, _MM_SHUFFLE(3,2,3,2));
I2 = _mm256_shuffle_ps(T2, T3, _MM_SHUFFLE(1,0,1,0));
I3 = _mm256_shuffle_ps(T2, T3, _MM_SHUFFLE(3,2,3,2));
I4 = _mm256_shuffle_ps(T4, T5, _MM_SHUFFLE(1,0,1,0));
I5 = _mm256_shuffle_ps(T4, T5, _MM_SHUFFLE(3,2,3,2));
I6 = _mm256_shuffle_ps(T6, T6, _MM_SHUFFLE(1,0,1,0));
I7 = _mm256_shuffle_ps(T7, T7, _MM_SHUFFLE(3,2,3,2));
T0 = _mm256_permute2f128_ps(I0, I4, 0x20);
T1 = _mm256_permute2f128_ps(I1, I5, 0x20);
T2 = _mm256_permute2f128_ps(I2, I6, 0x20);
T3 = _mm256_permute2f128_ps(I3, I7, 0x20);
T4 = _mm256_permute2f128_ps(I0, I4, 0x31);
T5 = _mm256_permute2f128_ps(I1, I5, 0x31);
T6 = _mm256_permute2f128_ps(I2, I6, 0x31);
T7 = _mm256_permute2f128_ps(I3, I7, 0x31);
_mm256_store_ps((dst + ((x + 0) * h) + y), T0);
_mm256_store_ps((dst + ((x + 1) * h) + y), T1);
_mm256_store_ps((dst + ((x + 2) * h) + y), T2);
_mm256_store_ps((dst + ((x + 3) * h) + y), T3);
_mm256_store_ps((dst + ((x + 4) * h) + y), T4);
_mm256_store_ps((dst + ((x + 5) * h) + y), T5);
_mm256_store_ps((dst + ((x + 6) * h) + y), T6);
_mm256_store_ps((dst + ((x + 7) * h) + y), T7);
}
}