# 2017q1 Homework3 (software-pipelining) contributed by < `jack81306` > ###### tags: `jack81306` ## 論文筆記 ### SW prefetch 優點 * hw prefetcher 難以處理太複雜的 prefetch,但是 sw prefetch 可以. * hw prefetcher 需要 cache missed 來訓練,但是當遇到 short stream 時便難以發揮,此時 sw prefetch 可以處理. * sw prefetch 可以很好的處理不規則資料. * 程式設計者可以自己決定要把資料放到那一層 cache. * hw prefetch 通常放到 l2 l3 以降低 pollution * 可以自行判斷回圈是否到底並不超出範圍 prefetch . ### SW prefetch 缺點 * 指令數增加 * 不能動態的改變 prefetch distacne * 當 loop 過小時可能難以插入 prefetch instruction.或者是需要改變結構. ### 實驗結果 * 指令增加主要是 sw prefatch 的缺點 * 額外的指令除了 prefetch 指令外,還有處理邊界,或者是計算記憶體位子的指令. * 儘管 gemesftdt 的指令增加超過 50 %,整體效能仍然是增加的,因為 prefetch 所帶來的增益超過指令增加的損失. * 為了檢測是那種 sw overhead 影響效能最多,分別移除各種造成緩慢的原因進行測試. * 移除 cache pollution :造成 cache pollution 的原因有可是過早或錯誤的 prefetch.移除後對實驗並無顯著影響,代表 cache pollution 並非影響效能的主因. * cache pollution 是指正在執行的程式將不必要的資料從 memory 移到 cache,降低了資料處理效率的現象 * 移除 bandwidth consumption :提升的效能十分的小,代表記憶體頻寬仍然夠用. * 移除 latency : 提升的效能十分的大!因為 sw prefetch 並沒有完全的把 memory latency 隱藏起來. * 移除 redundant prefetch : bwaves 受到蠻大的影響,但是影響其他程式效能小到可以忽略不計. * 移除 instruction overhead :對前面實驗增加最多 instruction 的程式並沒有顯著的增加效能,反倒是 libquantum ,再前面實驗並沒有增加顯著的 instruction,卻再這裡提升非常多的效能. #### 觀察 prefetch distance 對效能的影響 * 將資料分為五組,並將各個程式對 prefetch distance 的敏感度分配. #### static distance vs. machine configure * static distance 的限制是無法對機器的變化做出最好的距離設定. #### 使用不同 machine configure 實驗最佳 prefetch distance * 除了 lbm 其他並無明顯的效能增加. * 結論: machine configure 對最佳 prefetch distance 的效能並無顯著影響. ### Cache-Level Insertion Policy * 大部分的 cpu 可以容忍 l1 cache missed ,因此 hw prefetcher 通常將資料放到後面一點的 cache ,以免汙染 l1 cache. * 太多的 l1 cache missed 仍會降低效能. * sw prefetch 可以精準的將資料放進 l1 cache. ##### cache level 實驗 * 實驗使用 T0 cache level hint (也就是將資料放進三層 cache). * T0 效能較高是因為降低 l1 cache miss. * 通常 T0 效能會比其他來的高是因為可降低 l1 cache miss. ##### hw prefetch 結合 sw prefetch * 使用 sw prefetch 幫忙訓練 hw prefetch * 結論: 雖然有些會效能會成長,但是一般通常會造成效能嚴重下降. * coverage 太低是造成效率無法提升的原因 ### 使用 gcc 做實驗 * 因為 gcc 不支援 Fortan intrinsic 所以前面都是使用 icc.並將 intrinsic 再編譯後插入. * 移除使用 fortan 的 benchmark. * gcc 插入更多的 sw prefatch 除了 mcf 和 lbm . * gcc 無法處理所有的 cache miss ,效能再 nosw 和 sw 之間. ### 使用真實系統實驗 * 使用 intel core2 和 Nehalem * 使用全部的程式碼而非只有 simpointed 的部份 > simpointed ??? * 除了 milc,gcc,zeusmp 之外,大部分摹擬的程式和整份程式有差不多的 prefetch 指令. * 模擬系統的配置接近 core2 * 有幾個程式會再真實系統裡效能下降. * 再 positive group 裡的程式效能都有上升. * Nehalem 的 hw prefetch 的效能大於 core2 ,而且,由於 Nehalem 有 mutiple cache level 導致 sw prefetch 的效能增加量下降. ### Profile-Guided Optimization #### 何謂 PGO? * PGO (Profile Guided Optimization) 是軟體開發中的其中一項最佳化技術,藉由完整程式碼的分析,將常用的程式碼與少用的程式碼進行封裝與拆離,減少因為執行期間 (runtime) 動態連結 (Dynamic Linking) 對執行效率帶來的影響,此外,PGO 技術也最佳化了記憶體使用方式,將常用的功能放入特定記憶體區塊,提高 CPU 指令集快取命中率,進而提升整體執行效能。 #### PGO 實驗結果 * PGO 的效能位在 SW prefetch 和 base 之間. * PGO 效能的優化不只是 SW prefetch ,還有其他優化方式. * PGO 的指令數比 BASE 還少. ### Hardware Prefetcher for Short Streams * HW prefetch 的缺點是難以處理 short streams. * 解決辦法 : ASD HW prefetcher (仍未實作,僅為概念) * 儘管 ASD 在 short stream 可以比一般的 HW prefetch 還快,但是仍然比 sw prefetch 還要慢. > 到底什麼是 asd prefetcher 咧? ### Content Directed Prefetching * CDP 通常用來處理不規則的資料結構 * 雖然 CDP 有提升這些不規則的資料結構,但是仍然無法超越 SW prefetch (78% 效能提升) 的增益. > 看不太懂 CDP 的意思 ### 手動調整 sw prefetch * 因為我們無法確定程式是不是已經最優化了,因此要窮進手段來對程式進行優化. * 通常的方法有移除未始用的 prefetch ,調整 prefetch distance ,增加其他的優化技術,如 loop unrolling 和 prologue/epilogue transformations ? > 什麼是 prologue/epilogue transformations ? ### 論文總結 * sw prefetch 對某些 regular access pattern 比 hw prefetch 更有效率,如 short stream. * 就算 hw configure 對 prefetch distance 不敏感,仍要小心設定. * 儘管 ooo cpu 對 la cache missed 容忍率高,但是當l1 cache missed 超過 20% 時 ,可以藉由 prefetch 降低 l1 cache missed 進行優化. * 無用的 prefetch instruction 對效能不太造成影響. * 要小心的對 hw prefetch 訓練,否則容易造成效能降低. ## 實驗紀錄 ### 對原始碼進行測試 * <big>naive</big> ``` 17,142,240 cache-misses ( +- 0.48% ) 1,448,569,562 instructions # 1.10 insn per cycle ( +- 0.00% ) 1,316,109,257 cycles ( +- 1.23% ) 71,907 L1-icache-load-misses ( +- 4.39% ) 16,470,337 LLC-load-misses ( +- 0.07% ) 0.424526315 seconds time elapsed ( +- 1.25% ) ``` * <big>sse</big> ``` Performance counter stats for './sse' (5 runs): 4,874,075 cache-misses ( +- 1.74% ) 1,236,561,529 instructions # 1.35 insn per cycle ( +- 0.00% ) 917,252,607 cycles ( +- 1.34% ) 71,434 L1-icache-load-misses ( +- 10.83% ) 4,179,530 LLC-load-misses ( +- 0.17% ) 0.300347588 seconds time elapsed ( +- 1.61% ) ``` * <big>sse_prefetch</big> ``` 4,678,095 cache-misses ( +- 0.95% ) 1,282,795,897 instructions # 1.79 insn per cycle ( +- 0.01% ) 715,914,080 cycles ( +- 1.10% ) 68,664 L1-icache-load-misses ( +- 9.53% ) 4,188,387 LLC-load-misses ( +- 0.21% ) 0.232151802 seconds time elapsed ( +- 1.46% ) ``` * 可以從這結果中觀察到,因為 sse 和 sse_prefetch 的 cahce miss 和 instruction 都比 naive 少,因此會使用 sse 的結果會比 naive 快上許多. * 但是,有疑問的是,sse_prefetch 和 sse 的測試結果差不多,可是時間卻差了將近 0.07 秒. ### 進一步分析 * 從 [kaizsv的共筆](https://hackmd.io/s/r1IWtb9R) 中得知,perf 中的 event 會隨著處理器架構不同而不同,因此要去 [Intel® 64 and IA-32 Architectures Developer’s Manual: Vol. 3B](http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3b-part-2-manual.html) 中察看自己的處理器以及想要觀察的事件號碼. * 以下是想要觀察的事件 * MEM_LOAD_UOPS_RETIRED.L1_MISS:Retired load uops missed L1 cache as data sources(D1H,08H) * MEM_LOAD_UOPS_RETIRED.L2_MISS:Retired load uops missed L2. Unknown data source excluded.(D1H,10H) * MEM_LOAD_UOPS_RETIRED.L3_MISS:Retired load uops missed L3. Excludes unknown data source (D1H,20H) > 不太明白 UOPS 的意義,但就其他的敘述應該可以判斷這些指令是在偵測 L1 L2 L3 cache missed 的? ### 重新測試 * <big>naive</big> ``` Performance counter stats for './naive' (5 runs): 16,849,839 cache-misses ( +- 0.19% ) (25.47%) 1,446,964,134 instructions # 1.12 insn per cycle ( +- 1.45% ) (38.36%) 1,296,977,715 cycles ( +- 0.89% ) (50.89%) 74,503 L1-icache-load-misses ( +- 3.57% ) (50.75%) 16,368,952 LLC-load-misses ( +- 0.84% ) (63.21%) 16,407,896 r08d1 ( +- 0.79% ) (60.06%) 15,712,873 r10d1 ( +- 1.90% ) (24.53%) 15,386,702 r20d1 ( +- 1.54% ) (24.90%) ``` * <big>sse</big> ``` Performance counter stats for './sse' (5 runs): 4,433,999 cache-misses ( +- 3.76% ) (24.92%) 1,241,345,259 instructions # 1.43 insn per cycle ( +- 1.55% ) (37.90%) 870,806,366 cycles ( +- 1.70% ) (50.51%) 72,335 L1-icache-load-misses ( +- 5.73% ) (51.22%) 4,036,982 LLC-load-misses ( +- 1.85% ) (64.11%) 4,226,923 r08d1 ( +- 1.90% ) (59.56%) 3,997,999 r10d1 ( +- 2.79% ) (24.59%) 3,882,275 r20d1 ( +- 2.22% ) (24.57%) 0.290662977 seconds time elapsed ( +- 1.37% ) ``` * <big>sse_prefetch</big> ``` 4,855,312 cache-misses ( +- 2.83% ) (25.69%) 1,225,977,099 instructions # 1.70 insn per cycle ( +- 1.31% ) (38.77%) 719,123,446 cycles ( +- 0.61% ) (51.44%) 72,674 L1-icache-load-misses ( +- 5.77% ) (51.74%) 4,500,073 LLC-load-misses ( +- 4.51% ) (64.06%) 3,587,506 r08d1 ( +- 2.02% ) (58.10%) 88,901 r10d1 ( +- 28.17% ) (24.16%) 68,986 r20d1 ( +- 56.87% ) (25.29%) ``` * 測試結果中的 r08d1,r10d1,r20d1 分別是 L1 L2 L3 cache misses 的數量. * 從增加了判斷 cache miss 在哪一層發生的事件後,可以很明顯的看出來說 sse_prefetch 多半只有發生 L1 cache miss 且大部份都在 L2 cache hit,而 sse 則是發生了許多 L1 L2 L3 cache miss,這應該就是兩者速度差異的原因了 ### 封裝不同的實作及使用相同介面 * 將所有的 implement function 統一命名為 transpose ,並且使用 -D 參數來控制要使用那一個實作. * 下列程式碼中的 transpose 會因編譯時使用不同的參數而有不同的實作. ```likec struct timespec start, end; int *src = (int *) malloc(sizeof(int) * TEST_W * TEST_H); int *out = (int *) malloc(sizeof(int) * TEST_W * TEST_H); srand(time(NULL)); for (int y = 0; y < TEST_H; y++) for (int x = 0; x < TEST_W; x++) *(src + y * TEST_W + x) = rand(); clock_gettime(CLOCK_REALTIME, &start); transpose(src, out, TEST_W, TEST_H); clock_gettime(CLOCK_REALTIME, &end); printf("naive: \t\t %ld us\n", diff_in_us(start, end)); free(src); free(out); ``` * 修改 MAKEFILE 使其能分別產生不同的執行檔. ### 理解 SSE transpose * 根據 [許士傑的共筆](https://embedded2015.hackpad.com/-Homework-7-8-XZe3c94XjUh) 可以理解,每個 int element 為 32-bit,所以 4x4 矩陣的每一個 row 剛好可以打包成 128-bit,再透過高低位的 swap,就可以達到轉置的效果,運作方式如下圖。 ![](https://i.imgur.com/OiHqNcd.png) ### 使用 avx 進行改寫 * 因為 avx 使用的是 256bit register 因此,對於 int 來說,變成一次要處理 8x8 的矩陣. * 會使用的指令 * _mm256_unpacklo_epi32 * _mm256_unpacklo_epi64 * _mm256_permute2x128_si256 * 再我第一次寫程式的時候,我原本以為會有 _mm256_unpacklo_epi128 這條指令讓我來使用,後來發現居然沒有這條指令!! * 於是後來到了前幾屆的 [共筆](https://embedded2015.hackpad.com/ep/pad/static/VGN4PI1cUxh) 中,找到了 _mm256_permute2x128_si256 這條指令. * 這條指令的特別之處是可以藉由第3個參數,來決定 src1 和 src2 的前128 bit 或後 128 bit擺放順序.這樣的化就可以完成轉置矩陣的最後一個步驟. ### avx 測試結果 ``` Performance counter stats for './avx': 4,150,618 cache-misses (34.16%) 1,137,217,451 instructions # 1.67 insn per cycle (50.77%) 680,029,146 cycles (50.77%) 951,759 r08d1 (49.98%) 1,084,546 r10d1 (33.36%) 1,197,231 r20d1 (33.12%) ``` * 可以從這裡觀察出,avx 的 L1 L2 L3 cache misses 落點十分的平均,這和 sse 的結果大為不同. > 為何會有這樣的結果!!??? ### 比較圖表 ![](https://i.imgur.com/4yzHHSu.png) ## 參考資料 * [PGO](https://www.soft4fun.net/tech/news/google-chome-faster.htm)