# 2017q1 Homework3 (software-pipelining) contributed by <`ierosodin`> ### Reviewed by `jackyhobingo` * 在實驗的作圖上,橫軸的單位看起來很混亂,數字都擠在一起,可以考慮修改一下 * 在 [commit](https://github.com/ierosodin/prefetcher/commit/8ba05a2b8a1176e8016536bc217866cd111d5910) 上都只有一行說明,沒有明確說明為什麼改了就可以增進效能 * 有些標題下的內文還未出現,期待版本三的出現 ## 論文筆記 * hardware prefetching * stream prefetcher (STR) 當發生連續 data access cache miss 時,就會先將接下來連續的 data prefetch 進 stream buffer 。 * stride prefetcher (GHB) 不同於 stream prefetcher ,當發生固定間隔的 cache miss 時,預測接下來會 access 的 data ,進行 prefetching 。 而 global history buffer (GHB) 以 FIFO 記錄過去 data access 的 address ,並利用 hash table 作為 index 來減少空間的浪費,再以這些紀錄進行 prefetch 。 * software prefetching * icc * gcc * intrinsic _mm prefetch(char *ptr, int hint) * _MM HINT_T0 (all level caches) * _MM HINT_T1 (respect to first level cache) * _MM HINT_T2 (respect to second level cache) * _MM HINT_NTA (Non-temporal data) * 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 分類 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 * $D >= [\frac ls]$ where D is the prefetch distance, l is the prefetch latency and s is the length of the shortest path through the loop body * large enough to hide the latency * if it is too large, prefetched data could evict useful cache blocks, and the elements in the beginning of the array may not be prefetched * Direct and Indirect Memory Indexing * indirect memory indexing requires special hardware prefetching mechanisms * indirect indexing is relatively simpler to compute in software. So software prefetching is more effective than hardware prefetching. * Benefits of Software Prefetching * 在面對 Large Number of Streams 時,由於 HW prefetcher 一次能 prefetch 的數量有限,不像 SW prefetcher 的靈活度較高。 * 然而 HW prefetcher 通常至少需要兩次的 cache misses 來 training ,因此 Short Streams 在 HW prefetcher 上的效能也較 SW prefetcher 來得不理想。 * Irregular Memory Access 對 HW prefetcher 來說較複雜,不容易進行 prefetch 。 * HW prefetcher 通常會將 data prefetch 進 lowerlevel cache,例如 L2、L3 ,而從 L2 到 L1 又是一筆效能支出(雖然這能減少 cache pollution),相較於 SW prefetcher ,提供 Cache Locality Hint 來更彈性的進行 data prefetch。 * 如何決定 Loop Bounds 對 HW prefetcher 來說是困難的,而 SW prefetcher 則可以使用像是 loop unrolling、sofeware pipelining 等來預防 prefetch 超出 array bounds。 * Negative Impacts of Software Prefetching * 由於 SW prefetcher 需要花費額外的支出在指令的 fetch and execution ,因此會增加指令的數量。 * SW prefetcher 無論是 compiler 或是 intrinsic ,都是事先決定好 prefetch distance ,然而像是 memory latency 都是會隨時間變化的。有些 HW prefetcher 則可以對此作出相對應的改變。 * 當 loop 中要執行的 instruction 數量較少時, SW prefetcher 可能會發生來不及 prefetch 的問題。 * Synergistic Effects * HW 與 SW 可以分別處理不同的 data structure,像是 HW prefetcher 處裡 regular streams ,而 SW prefetcher 則處理 irregular streams 。 * SW prefetcher 的動作可以用來 training HW prefetcher ,讓 HW prefetcher 知道何時要 prefetch 。 * Antagonistic Effects * 如果 SW prefetcher 跳過部分的 streams ,將會影響 HW prefetcher 的 training ,且過度的 training 也會造成反效果。 * SW prefetcher 通常比 HW prefetcher 來得精準,因此若 SW prefetcher 發生 incorrect 或是 early ,將降低 HW prefetcher 的效益。 ### EXPERIMENTAL ## Transpose Matrix ### 修改 Makefile 改成分別產生 `naive_transpose` `sse_transpose` `sse_prefetch_transpose` 執行檔,並用 -D 來辨別實作的方法。 ### 封裝技巧 修改 impl.c ,用 `#ifdef` 來區別不同的 transpose 方法,這樣在 main.c 中,同樣是呼叫 transpose() ,會因為不同的 define ,而呼叫不一樣的實作函式。 ### How SSE transpose Matrix SEE 儲存變數的 type 是 __m128i ,因此可以存 4 的整數(32 bits)。 * `__m128i _mm_loadu_si128 (__m128i *p);` 在每一個 for 迴圈中,會呼叫 4 次 load 來儲存 4 個 row 的資料,也就是一個 4*4 的矩陣。 |***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);` 會接收兩個 128 bits 的 data ,會取出低位的兩組 32 bits,並做交叉組合 * `__m128i _mm_unpackhi_epi32 (__m128i a, __m128i b);` 會接收兩個 128 bits 的 data ,會取出高位的兩組 32 bits,並做交叉組合 |***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);` 會接收兩個 128 bits 的 data ,會取出低位的一組 64 bits,並做交叉組合 * `__m128i _mm_unpackhi_epi64 (__m128i a, __m128i b);` 會接收兩個 128 bits 的 data ,會取出高位的一組 64 bits,並做交叉組合 |***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);` 將轉置後的 data 存回陣列 |***I0***|**01**|**11**|**21**|**31**| |:-:|:-:|:-:|:-:|:-:| |***I1***|**02**|**02**|**22**|**32**| |***I2***|**03**|**13**|**23**|**33**| |***I3***|**04**|**14**|**24**|**34**| > 從 SSE 實作 transpose 的方法可以發現,矩陣的邊長必須是 4 的倍數![name=ierosodin][color=green] ### 效能分析 以 gnuplot ,畫出三種方法矩陣邊長對時間的折線圖: ![](https://i.imgur.com/LevKVaC.png) 可以發現使用 prefetch 明顯的提升了轉置矩陣的速度,但 naive 與 sse 兩者的效能卻很難比較,並發現 sse 矩陣邊長對時間的震盪很明顯且似乎有規律!拉出來分析: ![](https://i.imgur.com/pVDBhct.png) 這是從邊長 1600 開始,每次加 4 所得到的結果,可以發現,邊長為 8 的倍數的表現明顯較好 > 是 cache 的行為造成的?思考如何解釋這個現象...[name=ierosodin][color=green] ### perf 分析 cache-misses ### AVX 有關 SIMD 語法的使用,可以參考[The Intel Intrinsics Guide](https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=417,406,3129,565,5145,3135,417,406,3893&techs=AVX) #### 版本一:仿照 SSE 的方法,實作 8*8 矩陣的 transpose ```clike= 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 不是免費的阿!!![name=ierosodin][color=green] #### 版本二 利用 _mm_256_shuffle_ps 代替 _mm256_unpacklo_pd ,這樣可以免去資料型態的轉換 ```clike= 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); } } ``` #### 版本三 ### prefetch distance ### 非方形矩陣