# 2016q3 Homework3 (software-pipelining) contributed by <`ktvexe`> - [ ]學習計算機結構並且透過實驗來驗證所學 - [ ]理解 prefetch 對 cache 的影響,從而設計實驗來釐清相關議題 - [ ]論文閱讀和思考 ## 作業要求 * 閱讀 [在計算機裡頭實踐演算法](/s/HyKtIPN0) 提到的論文: "[When Prefetching Works, When It Doesn’t, and Why](http://www.cc.gatech.edu/~hyesoon/lee_taco12.pdf)" (務必事先閱讀論文,否則貿然看程式碼,只是陷入一片茫然!),在 Linux/x86_64 (注意,要用 64-bit 系統,不能透過虛擬機器執行) 上編譯並執行 [prefetcher](https://github.com/embedded2015/prefetcher) * 說明 `naive_transpose`, `sse_transpose`, `sse_prefetch_transpose` 之間的效能差異,以及 prefetcher 對 cache 的影響 * 在 github 上 fork [prefetcher](https://github.com/embedded2015/prefetcher),嘗試用 AVX 進一步提昇效能 * 修改 `Makefile`,產生新的執行檔,分別對應於 `naive_transpose`, `sse_transpose`, `sse_prefetch_transpose` (學習 [phonebook](s/S1RVdgza) 的做法) * 用 perf 分析 cache miss/hit * 學習 `perf stat` 的 raw counter 命令 * 參考 [Performance of SSE and AVX Instruction Sets](http://arxiv.org/pdf/1211.0820.pdf),用 SSE/AVX intrinsic 來改寫程式碼 * 詳細描述實驗設計,以及你的觀察 ## 複習計組 >紀錄:翻閱了算盤書,想回憶書中對prefetch的描述,赫然發現書中充滿了AVX、SIMD等關鍵字,我過去一直以為我是在phonebook中認識這些技術,我的大二到底怎麼了...[name=劉亮谷][color=#c63eef] ## When Prefetching Works, When It Doesn’t, and Why ### Introduction 雖然有很多種software 或是 hardware prefetching的機制可以應付cache miss的可能,但在編譯器中,只有實作一些比較簡單的software prefetching演算法,所以說:注重效能的工程師會使用 prefetching intrinsics function來撰寫程式。 但這樣做也是有問題存在,第1點是這種方式並沒有嚴謹的規則可以描述何種插入 intrinsics funciton的方法最能真正提高效能;再者,software 與 hardware prefetching間互動的複雜度我們是不清楚的。 ## Performance of SSE and AVX Instruction Sets 這份文件簡介中很清楚的介紹SIMD與SSE/AVX instruction,以下節錄: SSE (streaming SIMD extensions)和 AVX (advanced vector extensions) 都是 SIMD (single instruction multiple data streams)的 instruction sets SIMD可讓多核的CPU進行平行處理,基本的資料搬移與算術運算指令可以被同時處理;雖然像GNU與Intel compiler皆俱備 SIMD optimization的選項,但使用適當的SIMD programming技巧,如: data packing, data reuse and asynchronous data等,可以達到更加的效能。 ### Introduction SIMD不同於SISD,他一個指令可以同時對多個data進行操作,提供了CPU層級的平行處理,直接舉例就是:同時對數個數值做加法,或是更快完成向量積。 要實作SIMD文中提到三種作法,分別是:inline assembly、 intrinsic function、 vector class * inline assembly:這種方法當然最複雜,但是優點就是可以清楚掌握每個過程的進行(好比手刻的compiler跟lex/yacc)。 * intrinsic function: 這提供了C/C++語言的介面,可以直接產生assembly code,但缺點是不保證會有最高度的最佳化。 * Vector class:使用vector class的優點是比intrinsic function更容易撰寫,但效能也會比較差。 SSE提供16支XMM registers (xmm0 ∼ xmm15)分別各有128bits AVX將原本SSE的128bits暫存器擴增至256bits,稱為 YMM registers(ymm0 ∼ ymm15) 不過AVX其實已經進展到AVX-512(@Champ Yenvm學長的投影片中提及),很可惜我的電腦不支援,這次無法實驗。 ![](https://i.imgur.com/ljewRAl.png) 此外AVX提供三組input arguments,而SSE只有兩組,所以說SSE只支援a=a+b的操作,而AVX可以辦到a=b+c的操作。 ### Optimization Scheme * Data Packing 這段落很有趣,看到程式碼:他分別一次取 256bits的資料作copy,可以看到操作次數上有所差異,然而最有趣的地方是,如果對C code version開最佳化,他的效能是與SIMD相同的,可以用objdump將最佳化結果倒出來,會發先開最佳化後,會自動將此操作轉為SIMD;而且比較AVX與SSE會發現,效能上並沒有明顯的差異,由此可見YMM registers並沒有辦法提升資料搬移的速度。 C code ```clike= for(i=0 ; i< ArraySize ; i+=8){ b[i] = a[i]; b[i+1] = a[i+1]; b[i+2] = a[i+2]; b[i+3] = a[i+3]; b[i+4] = a[i+4]; b[i+5] = a[i+5]; b[i+6] = a[i+6]; b[i+7] = a[i+7]; } ``` > 我的天阿,他不給我syntax highlight[name=劉亮谷] > 原來小於後面需要空白,不然HackMD會parsing error[name=劉亮谷][color=#c63eef] SSE: ```clike= for(i=0; i< ArraySize; i+=8} { asm volatile ( "movups %2, %%xmm0 \n\t" "movups %%xmm0, %0 \n\t" "movups %3, %%xmm1 \n\t" "movups %%xmm1, %1 \n\t" : "=m" (b[i]), "=m" (b[i+4]) : "m" (a[i]), "m" (a[i+4]) ); } ``` AVX: ```clike= for(i=0; i< ArraySize; i+=8} { asm volatile ( "vmovups %1, %%ymm0 \n\t" "vmovups %%ymm0, %0 \n\t" : "=m" (b[i]) : "m" (a[i]) ); } ``` 效能比較: | |C++|SSE4.2|AVX1| |---|---|----|----| |no opt.|163|94.5|97.7| |max opt.|75|71|75| --- * Data Reuse 要講reuse還是用範例講解最易懂:因為SSE與AVX不需重複將資料搬進reg中,而是重複使用相同register,所以再效能上會比沒有使用SIMD的版本有明顯的提升。 C code ```clike= for(i=0; i < LoopNum; i++) { c[0] = a[0] + b[0]; c[1] = a[1] + b[1]; c[2] = a[2] + b[2]; c[3] = a[3] + b[3]; c[4] = a[4] + b[4]; c[5] = a[5] + b[5]; c[6] = a[6] + b[6]; c[7] = a[7] + b[7]; } ``` SSE ```clike= asm volatile ( ... "LOOP: \n\t" "addps %%xmm0, %%xmm1 \n\t" "addps %%xmm2, %%xmm3 \n\t" "dec %%ecx \n\t" "jnz LOOP \n\t ... ``` AVX ```clike= asm volatile ( ... "LOOP: \n\t" "vaddps %%ymm0, %%ymm1, %%ymm2 \n\t" "dec %%ecx \n\t" "jnz LOOP \n\t" ... ``` 效能比較: | |C++|SSE4.2|AVX1| |---|---|----|----| |no opt.|732|83.7|26.9| |max opt.|317|78.7|26.9| >其實我看不懂AVX在這邊比SSE還要快的原因[name=劉亮谷] --- * Asynchronous Data Transfer 將資料從記憶體搬到register是非常緩慢的動作,所以說以data packing的方式,是很難真正得到效能改善,不過Asynchronous Data Transfer是種同時處理運算與資料搬移的技術,可以將資料搬移的負載減少。 SSE與AVX都提供prefetching的方法,prefetching就是在CPU真正運算前,將資料先辦進cache,不過要注意的是,SSE與AVX皆無法強制data做prefetching,只能提供資訊給CPU做選擇,原因是有可能有更高優先權的process(有可能來自OS或其他)需要被處理。 效能比較: | |C++|SSE4.2|AVX1| |---|---|----|----| |no prefetching|75|71|75| |512 bytes prefetching||69.9|70.9| 這邊的結論是:我們需要更powerful的 asynchronous data transfer method >這邊看到蠻傻眼的,前面描述好像很厲害,結果卻無法有明顯的效能提升[name=劉亮谷][color=#c63eef] ## Experiment Evaluation ### Origin: ```shell= ktvexe@ktvexe-SVS13126PWR:~/sysprog21/prefetcher$ ./main 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 4 8 12 1 5 9 13 2 6 10 14 3 7 11 15 sse prefetch: 88613 us sse: 156130 us naive: 296468 us ``` >~~在perfetch下速度有所提升,不過經過前面的資料閱讀,我猜測這應該是再資料量與運算量少時的假象。~~[name=劉亮谷] ### cache miss: * naive ```shell Performance counter stats for './naive' (100 runs): 18,929,950 cache-misses # 93.514 % of all cache refs (66.28%) 20,315,136 cache-references (66.48%) 21,216,594 L1-dcache-load-misses (66.79%) 4,127,598 L1-dcache-store-misses (67.16%) 53,629 L1-dcache-prefetch-misses (67.13%) 66,024 L1-icache-load-misses (66.54%) 0.469926622 seconds time elapsed ( +- 0.60% ) ``` * SSE ```shell Performance counter stats for './SSE' (100 runs): 6,892,848 cache-misses # 90.614 % of all cache refs (66.29%) 7,713,047 cache-references (66.55%) 8,645,086 L1-dcache-load-misses (66.87%) 3,996,170 L1-dcache-store-misses (67.20%) 84,852 L1-dcache-prefetch-misses (67.09%) 166,184 L1-icache-load-misses (66.50%) 0.333443107 seconds time elapsed ( +- 0.80% ) ``` * SSE_PF ```shell Performance counter stats for './SSE_PF' (100 runs): 6,681,287 cache-misses # 87.202 % of all cache refs (65.92%) 7,349,026 cache-references (66.51%) 8,312,963 L1-dcache-load-misses (67.10%) 3,965,986 L1-dcache-store-misses (67.56%) 34,294 L1-dcache-prefetch-misses (67.32%) 63,104 L1-icache-load-misses (66.23%) 0.275961985 seconds time elapsed ( +- 0.97% ) ``` * 研究AVX 依樣畫葫蘆,對照SSE的模式, |Type |Meaning| |-------|-------| |__m256 |256-bit as eight single-precision floating-point values, representing a YMM register or memory location| |__m256d|256-bit as four double-precision floating-point values, representing a YMM register or memory location| |__m256i|256-bit as integers, (bytes, words, etc.)| |__m128|128-bit single precision floating-point (32 bits each)| |__m128d|128-bit double precision floating-point (64 bits each)| SSE中 `__m128i _mm_lddqu_si128 (__m128i const* mem_addr)` Description: Load 128-bits of integer data from unaligned memory into dst. This intrinsic may perform better than _mm_loadu_si128 when the data crosses a cache line boundary. `__m128i _mm_loadu_si128 (__m128i const* mem_addr) ` Description: Load 128-bits of integer data from memory into dst. mem_addr does not need to be aligned on any particular boundary. 相對應AVX中 `__m256i _mm256_lddqu_si256 (__m256i const * mem_addr)` Description: Load 256-bits of integer data from unaligned memory into dst. This intrinsic may perform better than _mm256_loadu_si256 when the data crosses a cache line boundary. `__m256i _mm256_loadu_si256 (__m256i const * mem_addr)` Description Load 256-bits of integer data from memory into dst. mem_addr does not need to be aligned on any particular boundary. >其實不是很清楚`lddqu`與`loadu`的差異,想增加一個`lddqu`的版本卻一直編不過。[name=ktvexe] ```shell cc -msse3 -msse2 --std gnu99 -O0 -Wall -g -DSSE_PF -o SSE_PF main.c In file included from main.c:17:0: impl.c: In function ‘sse_transpose2’: impl.c:39:26: warning: implicit declaration of function ‘_mm_lddqu_si128’ [-Wimplicit-function-declaration] __m128i I0 = _mm_lddqu_si128((__m128i *)(src + (y + 0) * w + x)); ``` >最後依照Intel Intrinsics Guide 上的Synopsis,加上`#include "pmmintrin.h"`終於編過了, 不過最終結果並沒有 ## Reference * [How to Vectorize Code Using C/C++ Classes on 32-Bit Intel® Architecture](https://software.intel.com/en-us/articles/how-to-vectorize-code-using-cc-classes-on-32-bit-intel-architecture) * [Programming trivia: 4x4 integer matrix transpose in SSE2](https://www.randombit.net/bitbashing/2009/10/08/integer_matrix_transpose_in_sse2.html) * [Introduction to Intel® Advanced Vector Extensions](https://software.intel.com/sites/default/files/m/d/4/1/d/8/Intro_to_Intel_AVX.pdf) * [Intel® Architecture nstruction Set Extensions Programming eference](https://software.intel.com/sites/default/files/managed/07/b7/319433-023.pdf) * [Intel Intrinsics Guide](https://software.intel.com/sites/landingpage/IntrinsicsGuide) * [SIMD Programming Introduction](https://docs.google.com/presentation/d/1LeVe7EAmZvqD3KN7p4Wynbd36xPOk9biBCFrdmsqzKs/edit#slide=id.p3) * [Hotball's HIVE](https://www.csie.ntu.edu.tw/~r89004/hive/sse/page_1.html) 未讀 * [Lattice gauge theory](https://en.wikipedia.org/wiki/Lattice_gauge_theory) * [許士杰共筆](https://embedded2015.hackpad.com/-Homework-7-8-XZe3c94XjUh) * [周曠宇共筆](https://embedded2015.hackpad.com/Week8--VGN4PI1cUxh)