# [compute-pi](https://hackmd.io/s/rJARexQT) [github](https://github.com/diana0651/compute-pi) contributed by <`Diana Ho`> ###### tags: `d0651` `sys` ## 案例分析 ### 學習目標 - [ ]學習透過離散微積分求圓周率 * [Leibniz formula for π](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80) * [積分計算圓周率π](http://book.51cto.com/art/201506/480985.htm) * [Function](http://www.csie.ntnu.edu.tw/~u91029/Function2.html) - [ ]著手透過 SIMD 指令作效能最佳化 ### 開發環境 Ubuntu 14.04 LTS --- ## Wall-clock time vs. CPU time * clock(), clock_gettime() 的使用 * clock() returns an approximation of processor time used by the program. ```clike= clock_t begin = clock(); compute_pi; clock_t end = clock(); double time = (double)(end - begin) / CLOCKS_PER_SEC; ``` * clock_gettime() retrieves the time of the specified clock clk_id. ```clike= struct timespec { time_t tv_sec; // seconds long tv_nsec; //nanoseconds } struct timespec start,end; clock_gettime(CLOCK_MONOTONIC_RAW,&start); compute_pi; clock_gettime(CLOCK_MONOTONIC_RAW,&end); double time = (double)((end.tv_sec-start.tv_sec)+(end.tv_nsec-start.tv_nsec)/1000000000); ``` * time(), gettimeofday() 的使用 * time() returns the wall-clock time from the OS, with precision in seconds. * gettimeofday() returns the wall-clock time with nominally µs precision. * 為什麼 clock_gettime() 結果飄忽不定? * function的執行時間單位以nanoseconds來決定,執行時間很短時並無法排除Linux背景處理其他事情,導致額外的cache miss,所以需要很多針對某一筆參數作累積再平均,才能避免飄忽不定 * 為什麼 time() 和 gettimeofday() 不適合拿來作 benchmark ? * clock_gettime()是monotonic,而time() 和 gettimeofday()的精準度較低,所以不適合 >>[參考概念](https://embedded2015.hackpad.com/HW1-Mj4fZIhTKVH) --- ## 效能分析比較圖表 預期執行 `$ make plot` 後,可透過 gnuplot 產生效能分析比較圖表 - 舊電腦: 每當執行到 avxunroll 時, 就會發生不合法命令的錯誤 ```clike= $ make check make: *** [check] Error 132 $ make gencsv Illegal instruction (core dumped) Illegal instruction (core dumped) Illegal instruction (core dumped) Illegal instruction (core dumped) Illegal instruction (core dumped) make: *** [gencsv] Error 132 $ time ./time_test_avxunroll 不合法的命令 (core dumped) real 0m1.219s user 0m0.000s sys 0m0.000s ``` :::warning > [參考](https://hackmd.io/KwUwLCAmBsYIwFoAcwCGBjBZICYDMyOkSCADEqgOwBmSIAnAEak5hA==) 如果在跑make check時發現下面的回應Illegal instruction (core dumped)代表電腦不支援AVX SIMD 不知道是什麼原因造成無法順利執行 舊電腦的開發環境: - `$ cat /etc/issue` Ubuntu 14.04.5 LTS \n \l - `$ cat /proc/version` Linux version 4.4.0-53-generic (buildd@lgw01-54) (gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) ) ::: - 新電腦: ```clike= gcc -c -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.c -o computepi.o gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DBASELINE -o time_test_baseline gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DOPENMP_2 -o time_test_openmp_2 gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DOPENMP_4 -o time_test_openmp_4 gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DAVX -o time_test_avx gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DAVXUNROLL -o time_test_avxunroll gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o benchmark_clock_gettime.c -o benchmark_clock_gettime time ./time_test_baseline N = 400000000 , pi = 3.141593 4.90user 0.00system 0:04.90elapsed 99%CPU (0avgtext+0avgdata 1760maxresident)k 0inputs+0outputs (0major+85minor)pagefaults 0swaps time ./time_test_openmp_2 N = 400000000 , pi = 3.141593 5.04user 0.00system 0:02.52elapsed 199%CPU (0avgtext+0avgdata 1780maxresident)k 0inputs+0outputs (0major+87minor)pagefaults 0swaps time ./time_test_openmp_4 N = 400000000 , pi = 3.141593 5.54user 0.00system 0:01.49elapsed 370%CPU (0avgtext+0avgdata 1784maxresident)k 0inputs+0outputs (0major+95minor)pagefaults 0swaps time ./time_test_avx N = 400000000 , pi = 3.141593 1.57user 0.00system 0:01.58elapsed 99%CPU (0avgtext+0avgdata 1768maxresident)k 0inputs+0outputs (0major+86minor)pagefaults 0swaps time ./time_test_avxunroll N = 400000000 , pi = 3.141593 1.70user 0.00system 0:01.70elapsed 99%CPU (0avgtext+0avgdata 1688maxresident)k 0inputs+0outputs (0major+85minor)pagefaults 0swaps ``` ```clike= gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DBASELINE -o time_test_baseline gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DOPENMP_2 -o time_test_openmp_2 gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DOPENMP_4 -o time_test_openmp_4 gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DAVX -o time_test_avx gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o time_test.c -DAVXUNROLL -o time_test_avxunroll gcc -O0 -std=gnu99 -Wall -fopenmp -mavx computepi.o benchmark_clock_gettime.c -o benchmark_clock_gettime for i in `seq 100 5000 25000`; do \ printf "%d," $i;\ ./benchmark_clock_gettime $i; \ done > result_clock_gettime.csv ``` :::info #### AVX [Introduction to Intel® Advanced Vector Extensions](https://software.intel.com/en-us/articles/introduction-to-intel-advanced-vector-extensions) The hardware supporting Intel® AVX (and FMA) consists of the 16 256-bit YMM registers YMM0-YMM15 and a 32-bit control/status register called MXCSR. ::: ### 使用 gnuplot 製圖 - 在 `runtime.gp` 中編輯繪圖語法 - 在 `Makefile` 中定義製圖的 rule ```clike= plot: default gnuplot runtime.gp ``` - 執行 `$ make plot` 產生`runtime.png` - 執行 `$ eog runtime.png` 看到圖像 ### 測量時間的函式 > [實作參考](https://hackmd.io/KwUwLCAmBsYIwFoAcwCGBjBZICYDMyOkSCADEqgOwBmSIAnAEak5hA==) 原來程式碼輸出的圖形(25倍時間),明顯看出資料分佈抖動的很厲害 clock 疊代了25次,所以應將程式改為取25次運算時間之平均 ```clike= clock_gettime(CLOCK_ID, &start); for(i = 0; i < loop; i++){ compute_pi_baseline(N); } clock_gettime(CLOCK_ID, &end); printf("%lf",(double)((end.tv_sec-start.tv_sec)+(end.tv_nsec-start.tv_nsec)/ONE_SEC))/loop); ``` #### 解法: 用信賴區間消去極端值 再將X軸取logscale :::info #### 信賴區間(Confidence interval) 由樣本資料定義一段數值區間,宣稱有多少信心以估計母體的參數包含於此區間內。 該數值區間上、下限稱為信賴界限(confidence limit)。 用以估計的信心程度稱為信賴(心)水準(confidence level)。 ##### 標準差 ![](https://i.imgur.com/tLEWvzY.jpg) * 一般常以 95% 或 99% 為信賴水準指標;相對應的 Z分數(相差幾個標準差) 分別為 1.96 與 2.58。 * 可表示為: 有 95% 信心估計母群體平均數,在樣本平均數 ± 1.96 * (母群體標準差 / 樣本數 n 的平方根) 的範圍內。 而 99% 信心估計母群體平均數,則在樣本平均數 ± 2.58 * (母群體標準差 / 樣本數 n 的平方根) 的範圍內。 科學符號表示方式: μ 之 95% CI = X ± 1.96 * ( σ / √n ) μ 之 99% CI = X ± 2.58 * ( σ / √n ) ::: - `clock()` ![](https://i.imgur.com/PcyQU11.png) - `clock_gettime()` ![](https://i.imgur.com/hjMtt82.png) - data 飄動, 嘗試增加 Makefile 定義 data 的資料數量: ```clike= for i in `seq 100 100 25000`; do \ printf "%d " $$i;\ ./benchmark_clock_gettime $$i; \ done > result_clock_gettime.csv ``` ![](https://i.imgur.com/KhFVewD.png) ### Error Rate :::info The higher the error rate, the less reliable the connection or data transfer will be. ::: * references: * [廖健富學長的筆記](https://hackpad.com/Hw1-Extcompute_pi-geXUeYjdv1I) - error rate的計算: 利用定義在`<math.h>`中的M_PI與computepi中的計算結果對比 --- ## baseline 版本 * pi 的公式 ![](https://i.imgur.com/3mUF7g8.png) * 黎曼積分原理 ![](https://hackpad-attachments.s3.amazonaws.com/charles620016.hackpad.com_M6oHPzPNlO2_p.431773_1442555734600_integral.png) * 程式碼: ```clike= 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; } ``` ## Leibniz 版本 ![](https://hackpad-attachments.s3.amazonaws.com/embedded2015.hackpad.com_Mj4fZIhTKVH_p.334595_1443717119937_pi.PNG) ### 實作 ```clike= double compute_pi_leibniz(size_t N) { double pi = 0.0; for(int i=0; i<N; i++) { int temp = (i%2) ? -1 : 1; pi += (double) temp / (2*i+1); } return pi * 4.0; } ``` ### 測試程式效能 ![](https://i.imgur.com/bRBSPOy.png) #### 測量 error rate ### 優化方向 ... - OpenMP - AVX - AVX + loop unrolling >>[實作參考](https://hackmd.io/GYZgRgnALAhgHAJgLRgCYIkqBWMMlwgwhIAMMA7AGzbBSkKpQRA=) ## Euler 版本 ![](https://i.imgur.com/32qQwAJ.png) ### 實作 ```clike= double compute_pi_euler(size_t N) { double pi = 0.0; for(int i=0; i<N; i++) pi += (double)(1 / pow(i, 2)); pi *= 6; return sqrt(pi); } ``` >>[實作參考](https://hackmd.io/GYZgRgnALAhgHAJgLRgCYIkqBWMMlwgwhIAMMA7AGzbBSkKpQRA=) ### 測試程式效能 ![](https://i.imgur.com/UfYL7Po.png) 若呼叫 `#include <math.h>` 的函式, 需要花的時間更長 #### 測量 error rate ### 優化方向 ... - OpenMP - AVX - AVX + loop unrolling ## Monte Carlo 版本 ... ### 實作 ```clike= int main() { double x,y; int count=0; /* # of points in the 1st quadrant of unit circle */ double z; double pi; count=0; for(size_t i=0; i<N; i++) { x = (double)rand()/RAND_MAX; y = (double)rand()/RAND_MAX; z = x*x+y*y; if (z<=1) count++; } printf("pi = %lf \n",(double)count/n*4); return 0; } ``` >>[實作參考](https://hackmd.io/MYFgHAJgDAjBCcBaAbAIwKZkeESzoHZhEBDYE9ECAJnjHAGYg===) ... --- ## SIMD ### 閱讀資料 [SIMD](https://docs.google.com/presentation/d/1LeVe7EAmZvqD3KN7p4Wynbd36xPOk9biBCFrdmsqzKs/edit#slide=id.p3) SIMD = Single Instruction Multiple Data,主要把原本一個指令中的數值分成多個部份,分配給多個執行緒執行完後再統整程最後結果,加速執行速度 #### SIMD 優化方式 - Auto/Semi-Auto method - 讓compiler做vectorization處理(ex: `#pragma`) - 把原本serial的執行變成平行化處理的方式 - [IR Optimization](https://web.stanford.edu/class/archive/cs/cs143/cs143.1128/lectures/14/Slides14.pdf): 對中間語言(intermediate representation)的優化 - 在從高階語言轉換產生IR後,時常會有一些冗餘的計算,應該優化 - 使用者會使用for loop來做一些型式類似並且多次的運算,減少所寫的code數量,所以compiler做loop unrolling達到優化 - Compiler Intrinsics - 利用intrinsics讓compiler編譯時,會被直接編譯成組合語言 - Specific Framework/Infrastructure - 為了平行化 - 直接利用data Parallel Framework,或是透過SIMD優化過的library - Coding in Assembly - 最極致的最佳化的方式 #### SIMD 困難的地方 - Finding Parallelism in Algorithm - 找出原本程式中可以做平行運算的部份 - Portability between different intrinsics - 不同架構中的轉換 - sse -> neon - neon -> sse - 在不同平台上就要使用不同的intrinscis,可攜性是一個問題 - Boundary handling - 資料邊界的對齊與填充 - ==Divergence== - Register Spilling - 電腦中register數量有限,所以變數會對應到register或memory上 - 編譯產生組合語言時,所使用的variable數量 > register的數量的情況發生 - ==Non-Regular Access/Processing Pattern/Dependency== - Unsupported Operations - 比較高階的函數 - Floating-Point - 浮點數運算跨裝置上的相容性問題 >>[參考概念](https://hackmd.io/BwVgxmCMBMYJwFoCmYAmBDBAWExgOA0XSQDYkRUR1TUB2MIA?both) >>[參考概念](https://hackmd.io/KYQwbMBMkAxgtAVgOwE5LwCwmAY3qogIwBm8kywAzIhCCDLrkA==?both) ### implement 效能最佳化 --- <style> h2.part {color: red;} h3.part {color: green;} h4.part {color: blue;} h5.part {color: black;} </style>