# 2016q3 Homework1(compute_pi) ## 目標 * 學習透過離散微積分求圓周率 * Leibniz formula for π * 積分計算圓周率π * Function * 著手透過 SIMD 指令作效能最佳化 * [作業說明](https://hackmd.io/JwdgZmCGBsDGCmBaAzPWAGRAWWtqOABNkAORESAVnUqzACMxkx0g#a03-compute-pi) ## Makefile 每寫一份功課又是學習Makefile的時候=w= * 編譯時有`-DBASELINE`等等,其中的`-D`指的是`#define BASELINE 1` * 這樣程式碼就可以塞在同一個.c .h中!只要在編譯的時候加上`-D`就好了!! * Makefile的篇幅可以大幅降低! * 我想phonebook也可以這樣改寫,不然不同的方法就還要在多生一組.c .h ## 實驗 * `make check`結果: ```shell== time ./time_test_baseline N = 400000000 , pi = 3.141593 7.59user 0.00system 0:07.59elapsed 100%CPU (0avgtext+0avgdata 1560maxresident)k 8inputs+0outputs (0major+82minor)pagefaults 0swaps time ./time_test_openmp_2 N = 400000000 , pi = 3.141593 8.03user 0.00system 0:04.01elapsed 200%CPU (0avgtext+0avgdata 1572maxresident)k 0inputs+0outputs (0major+84minor)pagefaults 0swaps time ./time_test_openmp_4 N = 400000000 , pi = 3.141593 8.32user 0.00system 0:02.16elapsed 384%CPU (0avgtext+0avgdata 1676maxresident)k 0inputs+0outputs (0major+89minor)pagefaults 0swaps time ./time_test_avx N = 400000000 , pi = 3.141593 3.30user 0.00system 0:03.30elapsed 100%CPU (0avgtext+0avgdata 1608maxresident)k 0inputs+0outputs (0major+83minor)pagefaults 0swaps time ./time_test_avxunroll N = 400000000 , pi = 3.141593 1.73user 0.00system 0:01.73elapsed 100%CPU (0avgtext+0avgdata 1608maxresident)k 0inputs+0outputs (0major+82minor)pagefaults 0swaps ``` * `make gencsv` ==> 輸出至表單 輸出結果與在Makefile中看到的程式碼只差在`$$i` 以下為輸出: ```shell== for i in `seq 100 5000 25000`; do \ printf "%d," $i;\ ./benchmark_clock_gettime $i; \ done > result_clock_gettime.csv ``` 原來除了寫進txt檔之外,還可以寫進libre office的表單裡,好酷! * 理解程式碼: * 上面的程式碼相當於: ```C== for(i=100; i<25000; i+=5000) { printf("%d",i); benchmake_clock_gettime(i); } ``` 然後把結果輸出至.csv檔 :::warning 細節的語法待會查 先highlight ::: * 在Makefile中,在執行各檔時前面多加了`time` * time: 查看指令的執行時間 * 直接執行`time ./time_test_baseline` ``` N = 400000000 , pi = 3.141593 real 0m7.805s user 0m7.808s sys 0m0.000s ``` * 顯示了三種時間:real,user和sys Real refers to actual elapsed time; User and Sys refer to CPU time used only by the process. * **real**: real time,a wall clock time,指令從開始到結束執行的時間,含其他process * **user**: user CPU time,指令在user mode的時間,其他process的時間不算 * **sys**: system CPU time,指令在kernel mode的時間,其他process的時間不算 * `real = user + sys` * ==**並非如此**==,如sleep(),不論在user mode或kernel mode沒佔用CPU時間,那就不會被算入,反觀real time仍會把sleep()的時間算進去 ### Baseline ![](https://i.imgur.com/H0I0Cig.png) 為上述積分式的實作,最後回傳的值要*4,因為算出來是 $π \over4$ * N愈大[0,1]就被切割成更多等份,dt也就愈小 ```C== double compute_pi_baseline(size_t N) { double pi = 0.0; double dt = 1.0 / N; // dt = (b-a)/N, b = 1, a = 0 for (size_t i = 0; i < N; i++) { double x = (double) i / N; // x = ti = a+(b-a)*i/N = i/N pi += dt / (1.0 + x * x); // integrate 1/(1+x^2), i = 0....N } return pi * 4.0; } ``` ### OpenMP * 也是用跟baseline一樣的積分式求得π * 可以傳遞參數決定要開幾個threads * 在`time_test.c`中分別傳入2個和4個threads的實驗 * 程式碼: * private(x): 讓每個thread都有自己的x,不然threads之間會把x當成共用的變數 * reduction(+:pi):讓各個thread針對指定的變數擁有一份有起始值的複本 * 支援的運算元有 +, *, –, &, ^, |, &&, ||;而變數則必須要是 shared 的 ```C== double compute_pi_openmp(size_t N, int threads) { double pi = 0.0; double dt = 1.0 / N; double x; #pragma omp parallel num_threads(threads) { #pragma omp for private(x) reduction(+:pi) for (size_t i = 0; i < N; i++) { x = (double) i / N; pi += dt / (1.0 + x * x); } } return pi * 4.0; } ``` ### AVX SIMD * 甚麼是AVX? * AVX (Advanced Vector Extensions) 是 Intel 一套用來作 Single Instruction Multiple Data 的指令集 * AVX指令集借鑑了一些AMD SSE5的設計思路,進行擴展和加強,形成一套新一代的完整SIMD指令集規範。 * SSE將向量處理能力從64位提升到128位後,SSE系列指令都只能使用128位XMM暫存器,這次AVX將所有16個128位XMM暫存器擴充為256位的YMM暫存器,從而支持256位的向量計算。 * prototype: * _mm256 : single precision,32-bit * _mm256d : double precision, 64bit 配上註解與下方的連結終於稍微看懂了~ * `__attribute__ `機制,可以用來設置函數屬性(Function Attribute)、變數屬性(Variable Attribute)和類型屬性(Type Attribute)。 * `aligned` 則規定變數或結構的最小對齊格式,以 Byte 為單位。 >不知道為甚麼沒有highlight@@ ```C== double compute_pi_avx(size_t N) { double pi = 0.0; double dt = 1.0 / N; register __m256d ymm0, ymm1, ymm2, ymm3, ymm4; ymm0 = _mm256_set1_pd(1.0); ymm1 = _mm256_set1_pd(dt); ymm2 = _mm256_set_pd(dt * 3, dt * 2, dt * 1, 0.0); ymm4 = _mm256_setzero_pd(); // sum of pi for (int i = 0; i <= N - 4; i += 4) { ymm3 = _mm256_set1_pd(i * dt); // i*dt, i*dt, i*dt, i*dt ymm3 = _mm256_add_pd(ymm3, ymm2); // x = i*dt+3*dt, i*dt+2*dt, i*dt+dt, i*dt+0.0 ymm3 = _mm256_mul_pd(ymm3, ymm3); // x^2 = (i*dt+3*dt)^2, (i*dt+2*dt)^2, ... ymm3 = _mm256_add_pd(ymm0, ymm3); // 1+x^2 = 1+(i*dt+3*dt)^2, 1+(i*dt+2*dt)^2, ... ymm3 = _mm256_div_pd(ymm1, ymm3); // dt/(1+x^2) ymm4 = _mm256_add_pd(ymm4, ymm3); // pi += dt/(1+x^2) } double tmp[4] __attribute__((aligned(32))); _mm256_store_pd(tmp, ymm4); // move packed float64 values to 256-bit aligned memory location pi += tmp[0] + tmp[1] + tmp[2] + tmp[3]; return pi * 4.0; } ``` * 程式碼解讀(可能有誤!!) * `__m256d`:存放double,256-bit可用,因此可以存放256/64 = 4 個double * 每次的運算是4個double **個別** 加總 * 最後使用tmp[4]把4個值存起來,最後在加總 * `(aligned(32))` 是因為double有8個byte,又宣告 double tmp[4] ==> 8*4= 32(byte) ### Leibniz ![](https://i.imgur.com/gdKlini.png) * 程式: ```C== double compute_pi_Leibniz(size_t N) { double pi = 0.0; double x; for (size_t i = 0; i < N; i++) { x = pow(-1,i) / (2*i +1); pi += x; } return pi * 4.0; } ``` 一直出現錯誤資訊: 應該是link的問題,但是即使加了`-lm`還是沒用@@ :::warning computepi.c:(.text+0xd80): 未定義參考到 pow collect2: error: ld returned 1 exit status make: *** [default] Error 1 ::: > computepi.c 有included <math.h>嗎? 或許可以試試,我花很多時間處理這個...[name=SarahYuHanCheng] * 只好先改成 ```C== int tmp = (i%2) ? -1 : 1; x = (double)tmp / (2*i +1) ; //記得轉型 pi += x; ``` ### Leibniz AVX 看了一些資料在搭配原有的程式碼,很快就可以寫出來了~ 覺得開心=w= ```C== double compute_pi_leibizavx(size_t N) { double pi = 0.0; register __m256d ymm0, ymm1, ymm2, ymm3, ymm4; ymm0 = _mm256_set_pd(1.0,-1.0,1.0,-1.0); //for pow(-1,i) ymm1 = _mm256_set1_pd(2.0); ymm2 = _mm256_set1_pd(1.0); ymm4 = _mm256_setzero_pd(); // sum of pi for (int i = 0; i <= N - 4; i += 4) { ymm3 = _mm256_set_pd(i, i+1.0, i+2.0, i+3.0); //i i+1 i+2 i+3 ymm3 = _mm256_mul_pd(ymm1, ymm3); //2*i 2*(i+1)... ymm3 = _mm256_add_pd(ymm3,ymm2); //2*i+1 2*(i+1)+1 ... ymm3 = _mm256_div_pd(ymm0,ymm3); //(-1)^i/(2*i+1) ... ymm4 = _mm256_add_pd(ymm3,ymm4); //sum } double tmp[4] __attribute__((aligned(32))); _mm256_store_pd(tmp, ymm4); // move packed float64 values to 256-bit aligned memory location pi += tmp[0] + tmp[1] + tmp[2] + tmp[3]; return pi * 4.0; } ``` ### 其他計算圓周率的方法 * [蒙地卡羅法](https://zh.wikipedia.org/wiki/%E8%92%99%E5%9C%B0%E5%8D%A1%E7%BE%85%E6%96%B9%E6%B3%95): * 邊長為2的正方形,正方形內有一個內切圓,面積為 $1 \cdot 1 \cdotπ = π$ * 圓面積和正方形面積之比為π:4 = 正方形內均勻隨機丟石頭,落在圓形內的機率 ```C== double compute_pi_MonteCarlo(size_t N) { int count = 0; srand(time(NULL)); for (size_t i = 0; i < N; i++) { double x = (double)rand()/RAND_MAX; double y = (double)rand()/RAND_MAX; if((x*x+y*y)<=1) count++; } return 4.0 * count / N ; } ``` ### Plot * 複習語法 * `using [a:b]` : 以第a column當x座標,第b column當y座標 製圖時產生的warning: :::warning warning: Skipping data file with no valid points ::: 查了一段時間,玄機就在副檔名`.csv`和gnuplot他吃數據的方式啊!! **csv : comma separated value** 所以資料之間是以 ==**逗號**== 區隔,因此要在gnuplot script裡加一行`set datafile separator ","`才讀的到資料 * 不同方法比較圖 :::warning 抖的超厲害! ::: 取樣從N=1000~N=250000,間隔1000 ![](https://i.imgur.com/A5GYP7c.png) 隔一天跑出來的結果差好多@@ omp_4沒那麼劇烈了,還不知道為甚麼會這樣 ![](https://i.imgur.com/GimWEAW.png) **新增Leibniz** * 可以發現Leibniz與omp_2的時間差不多,但又比omp_2還穩定 * LeibnizAVX也比avx的時間再更短 * LeibnizAVX_unroll語omp_4幾乎重合 ![](https://i.imgur.com/BI10Fxo.png) ### 思考如何精準計算時間 * walk clock time: 由xtime記錄,系統每次啟動時會先從設備上的 [RTC](https://zh.wikipedia.org/wiki/%E5%AF%A6%E6%99%82%E6%99%82%E9%90%98) 上讀入 xtime * xtime:從cmos電路中取得的時間,一般是從某一歷史時刻開始到現在的時間 * Jeffie:紀錄系統開幾以來,經過多少個 tick,取決於系統的頻率,單位是Hz * system time: * 系統上的時間,開機時它會讀取RTC 來設定,可能過程中還會有時區換算等之類的設置。 ### 信賴區間 ## 參考資料 [TempoJiJi hackmd](https://hackmd.io/KwBgpgxghhAcsFpYgGyICwEYXqZgJgEYL4DMmYAnAEz4Bmm41QA=?view) * Time * [time_ref1](http://yuanfarn.blogspot.tw/2012/08/linux-time.html) * [time_ref2](http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1) * [容易混淆LINUX時鐘的xtime和jiffies](http://www.coctec.com/docs/linux/show-post-186188.html) * OpenMP * [openmp_private](http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=17) * [openmp_reduction](https://kheresy.wordpress.com/2006/09/22/%E7%B0%A1%E6%98%93%E7%9A%84%E7%A8%8B%E5%BC%8F%E5%B9%B3%E8%A1%8C%E5%8C%96%EF%BC%8Dopenmp%EF%BC%88%E4%BA%94%EF%BC%89-%E8%AE%8A%E6%95%B8%E7%9A%84%E5%B9%B3%E8%A1%8C%E5%8C%96/) * AVX * [introduction](http://www.expreview.com/13236.html) * [manual](http://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-optimization-manual.pdf)(chap11) * [functions](https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=579,2050,100,4629,4578,112,100&techs=AVX) ###### tags: `system embedded` `HW1-3`