# 2017q1 Homework1(compute-pi) contributed by < `baimao8437` > ### Reviewed by stanleytazi + 信賴區間的公式目前看到的都是 standard error 下去算 ,而非standard deviation [confidence interval](https://en.wikipedia.org/wiki/Confidence_interval) + 從比較圖來看好像都有看到OpenMP with 4 threads有比較大的抖動,有點好奇為什麼 + default 設定跑25次來算,可以再試試看跑多次一點,看不同做法的時間會不會差的比較明顯 ### Reviewed by `hunng` - 利用多種方式計算並使用 AVX 實作 - 中間提到的不確定有沒有信賴區間,可以多次執行儲存結果後分析看看 - 而且有加上信賴區間比沒有加的抖動還要更大是不是出了什麼問題 ### 花費時間 ~2/28~ ~-~ ~3/2~ * 讀書:9 hr * 文件:4 hr * coding: 3 hr * ~~看著發呆:3 hr~~ ## 目標設定 * 習慣 linux 系統基本操作 * 熟練 git 基本版本控管操作 * 繪圖工具 LibreOffice、gnuplot * SIMD 實作練習 * 複習微積分? * Hackmd 文件 LaTeX 數學式寫法 * 減少發呆時間... ## 前置作業 這個作業一開始我整個霧煞煞 沒了老溼的精美影片教學 頓失依靠 只好先讀神人們的共筆了解一些基本名詞與時間分析 ## 開發環境 ``` baimao@baimao-Aspire-V5-573G:~$ lscpu Architecture: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 每核心執行緒數:2 每通訊端核心數:2 Socket(s): 1 NUMA 節點: 1 供應商識別號: GenuineIntel CPU 家族: 6 型號: 69 Model name: Intel(R) Core(TM) i5-4200U CPU @ 1.60GHz 製程: 1 CPU MHz: 1711.242 CPU max MHz: 2600.0000 CPU min MHz: 800.0000 BogoMIPS: 4589.38 虛擬: VT-x L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 3072K NUMA node0 CPU(s): 0-3 ``` ## SIMD指令集 - AVX (Advanced Vector Extensions) 是 Intel 一套用來作 Single Instruction Multiple Data 的指令集。 - 我的電腦 CPU 為 intel i5-4200U ![](https://i.imgur.com/f9Utdlh.png) - 使用時需`#include <immintrin.h>`,並在 Makefile 中的編譯選項加入`-mavx` ## 效能分析 測試原版程式效能: ```clike time ./time_test_baseline N = 400000000 , pi = 3.141593 4.40user 0.00system 0:04.40elapsed 99%CPU (0avgtext+0avgdata 1808maxresident)k 0inputs+0outputs (0major+80minor)pagefaults 0swaps time ./time_test_openmp_2 N = 400000000 , pi = 3.141593 4.91user 0.00system 0:02.46elapsed 199%CPU (0avgtext+0avgdata 1772maxresident)k 0inputs+0outputs (0major+80minor)pagefaults 0swaps time ./time_test_openmp_4 N = 400000000 , pi = 3.141593 9.76user 0.00system 0:02.45elapsed 397%CPU (0avgtext+0avgdata 1792maxresident)k 0inputs+0outputs (0major+85minor)pagefaults 0swaps time ./time_test_avx N = 400000000 , pi = 3.141593 1.26user 0.00system 0:01.27elapsed 99%CPU (0avgtext+0avgdata 1816maxresident)k 0inputs+0outputs (0major+82minor)pagefaults 0swaps time ./time_test_avxunroll N = 400000000 , pi = 3.141593 1.22user 0.00system 0:01.22elapsed 99%CPU (0avgtext+0avgdata 1816maxresident)k ``` 可以看到 user(user mode CPU time) 跟 system (kernel mode CPU time) 時間 也可以看到佔用了多少 CPU OpenMP 2 threads 最多佔用 200% CPU OpenMP 4 threads 最多就佔用 400% CPU 由於我還沒做好算數學的心理準備 就先來畫畫好了~ 想要建立出作業範例以 LibreOffice 建立的圖表(如下圖): ![](https://i.imgur.com/smW4YLk.png) 先到 Makefile 的 gencsv 看到 seq 100 5000 25000 等於是 ```for(i=100; i<25000; i+=5000)``` 我調成 ```seq 1000 200 400000``` 跑跑看將近兩千筆資料 然後把出來的 csv 以 LibreOffice Calc 開啟 選擇以逗號分隔 再把全部的資料選取好後 按下圖表 再做一些設定: 1. 圖表類型:設定為 線條圖 僅限線條 類型:平滑 2. 資料範圍:勾選 已欄表示的資料序列 + 第一欄作為標籤 3. 資料序列:把每條線名子改為對應 method 4. 圖表元素:x 軸為 N, y 軸為 Time(sec) :::danger 這份報告之後可以拿來做研究所推甄、工作實習,甚至可作為論文的雛形,用與請精確 --jserv ::: >Yes, Sir![name=baimao8437] 畫出來後: ![](https://i.imgur.com/kv0m2Lz.png) 預料之中 跟原圖差距甚遠 連一點指數上升的感覺都沒有 無法理解 而且原圖是 Baseline > OpenMP_2 > AVX > OpenMP_4 > AVX_unrolling 我的結果是 Baseline > OpenMP_4 > OpenMP_2 > AVX > AVX_unrolling 好像差了有點多 不過參考了別人的圖發現有人跟我一樣結果 就安心多了 補上 gnuplot perfomance + error rate: ![](https://i.imgur.com/iQktoFi.png) ![](https://i.imgur.com/UWXq7zF.png) ## 信賴區間 可看出圖表顯示有一些極端值造成線條的抖動 因此參考[學長的筆記](https://hackmd.io/s/HySsF65p) 試圖建立95信賴區間來排除極端值 機統沒學好 信賴區間忘光光: 簡單來說就這三句話 ``` 計算平均值 計算標準差(standard deviation) 平均值的正負標準差乘以 2 就是 95% 信賴區間 ``` 標準差長這樣: $$ s=\sqrt{{\frac{1}{n-1}\sum_{i=1}^n}(x_i-\bar x)^2} $$ 然後 makefile 中加入 ```-lm``` 一樣讓他跑 2000 筆資料 結果: ![](https://i.imgur.com/JPJJAQE.png) OpenMP 4 threads 還是抖了一下到讓我懷疑到底有沒有信賴區間... 補上 gnuplot: ![](https://i.imgur.com/er3Y3fI.png) ![](https://i.imgur.com/3ZuF0oJ.png) 可以看到 一樣的演算法 但是用不同的優化結果 AVX_unrolling 的優化會造成再某些 N 計算結果誤差比較大 其他的算法 error rate 皆相同 ## Leibniz 公式證明 證明: $$ \frac{\pi}{4}=1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+\frac{1}{9}-\cdots $$ 首先考慮 $$ 1-x^2+x^4-x^6+x^8-\cdots=\frac{1}{1+x^2},\ |x|<1 $$ 積分後 $$ x-\frac{x^3}{3}+\frac{x^5}{5}-\frac{x^7}{7}+\frac{x^9}{9}-\cdots=tan^{-1}x,\ |x|<1 $$ 接著證明收斂 $$ \frac{1}{1+x^2}=1-x^2+x^4-x^6+x^8-\cdots+(-1)x^{2n}+\frac{(-1)^{n+1}x^{2n+2}}{{1+x^2}} $$ 兩邊積分後等於 $$ \frac{\pi}{4}=1-\frac{1}{3}+\frac{1}{5}-\frac{1}{7}+\frac{1}{9}-\cdots+\frac{-1^n}{2n+1}+(-1)^{n+1}\int_0^1\frac{x^{2n+2}}{{1+x^2}}dx $$ 當$n\rightarrow\infty$時, 除積分項以外的項收斂到萊布尼茲級數, 同時積分項收斂至0 $$ \int_0^1\frac{x^{2n+2}}{{1+x^2}}dx<\int_0^1x^{2n+2}dx=\frac{1}{2n+3}\rightarrow0,\ when\ n\rightarrow\infty $$ 得證! $$\pi=4\sum_{n=1}^\infty\frac{(-1)^n}{2n+1}$$ 接著寫成 code: ```c= double compute_pi_leibniz(size_t N) { double pi = 0.0; int tmp = 1; for (size_t i = 0; i < N; i++) { pi += ((double)tmp / (2.0 * (double)i + 1.0)); tmp *= -1; } return pi * 4.0; } ``` >第一次寫用了 pow(-1,i)去變換正負 >但太花費時間 所以用 tmp 去變換正負 然後 Leibniz 優化挑了 AVX 來實作: ```c= double compute_pi_leibniz_avx(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);//represent tmp ymm1 = _mm256_set1_pd(1); ymm2 = _mm256_set1_pd(2); ymm4 = _mm256_setzero_pd(); // sum of pi for (int i = 0; i <= N - 4; i += 4) { ymm3 = _mm256_set_pd((double)i, (double)i + 1.0, (double)i + 2.0, (double)i + 3.0); // represent n ymm3 = _mm256_mul_pd(ymm2, ymm3); // [2*n] ymm3 = _mm256_add_pd(ymm1, ymm3); // 2*n[+1] ymm3 = _mm256_div_pd(ymm0, ymm3); // [tmp/](2*n+1) ymm4 = _mm256_add_pd(ymm4, ymm3); // pi += tmp/(2*n+1) } 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; } ``` 原本很畏懼 AVX 但有了上面的範例就好懂多了 也比較容易寫出來 * gnuplot 圖表 ![](https://i.imgur.com/YXsvZQM.png) 有點緊密 不過可以看到 AVX_unrolling 還是比 Leibniz_avx 快一點點 ![](https://i.imgur.com/hwK010B.png) 但是 error rate 卻明顯 Leibniz_avx 優於 AVX_unrolling 從輸出 csv 來看這邊 因為 Leibniz & Leibniz_avx 的 error rate 又全部跟其他重疊了 所以只有一條線 ## 其他算 $\pi$ 的方法 : Nilakantha method $$ \pi=3+\frac{4}{2\times3\times4}-\frac{4}{4\times5\times6}+\frac{4}{6\times7\times8}-\frac{4}{8\times9\times10}+\cdots $$ 寫成 code 也不難 大概長這樣 ```c= double compute_pi_nilakantha(size_t N) { double pi = 0.0; int tmp = 1; double start = 0.0; for (size_t i = 1; i < N; i++) { start = (double)i * 2.0; pi += (double)tmp / ((start) * (start + 1.0) * (start + 2.0)); tmp *= -1; } pi *= 4; pi += 3; return pi; } ``` 但是如果要用 AVX 優化的話 就要稍微細心一點了 改到頭昏腦脹 ```c= double compute_pi_nilakantha_avx(size_t N) { double pi = 0.0; register __m256d ymm0, ymm1, ymm2, ymm3, ymm4, ymm5, ymm6; ymm0 = _mm256_set_pd(1.0, -1.0, 1.0, -1.0); ymm1 = _mm256_set1_pd(1); ymm2 = _mm256_set1_pd(2); ymm3 = _mm256_set1_pd(4); ymm4 = _mm256_setzero_pd();//_mm256_set1_pd(4); for (int i = 0; i < N - 4; i += 4) { ymm6 = _mm256_set_pd((double)i + 1.0, (double)i + 2.0, (double)i + 3.0, (double)i + 4.0); //n ymm6 = _mm256_mul_pd(ymm2, ymm6); //start = i*2 ymm5 = _mm256_add_pd(ymm6, ymm1); //(start+1) ymm6 = _mm256_mul_pd(ymm6, ymm5); //start*(start+1) ymm5 = _mm256_add_pd(ymm5, ymm1); //(start+2) ymm6 = _mm256_mul_pd(ymm6, ymm5); //start*(start+1)*(start+2) ymm6 = _mm256_div_pd(ymm3, ymm6); // 4/start*(start+1)*(start+2) ymm6 = _mm256_mul_pd(ymm6, ymm0); ymm4 = _mm256_add_pd(ymm4, ymm6); } double tmp[4] __attribute__((aligned(32))); _mm256_store_pd(tmp, ymm4); pi += tmp[0] + tmp[1] + tmp[2] + tmp[3]; pi += 3.0; return pi; } ``` 我相信應該有更簡潔的寫法 但是至少這個結果是對的 來看圖表 ![](https://i.imgur.com/dnDFeIS.png) 效能來看最好的三個是 : (時間少) AVX > AVX_unrolling > Leibniz_avx (時間多) ![](https://i.imgur.com/uc2zLKr.png) 這個圖表看起來跟上面的 error 圖看起來一樣 因為 Nilakantha 的兩個算法 錯誤率都非常接近 0 錯誤率低的嚇人 我把 N 調小 單獨看 Nilakantha ``` N,error rate N,error rate 5,0.000608 55,0.000000 10,0.000079 60,0.000000 15,0.000023 65,0.000000 20,0.000010 70,0.000000 25,0.000005 75,0.000000 30,0.000003 80,0.000000 35,0.000002 85,0.000000 40,0.000001 90,0.000000 45,0.000001 95,0.000000 50,0.000001 100,0.000000 ``` 只要算到 50 幾項 就幾乎等於 $\pi$ 了 ## 參考資料: * [HankLo 的筆記](https://hackmd.io/s/HySsF65p) * [HaoTse 的筆記](https://hackmd.io/s/ryHY0T_T) * [廖建富學長筆記](https://hackpad.com/Hw1-Extcompute_pi-geXUeYjdv1I) * [萊布尼茲公式](https://zh.wikipedia.org/wiki/%CE%A0%E7%9A%84%E8%8E%B1%E5%B8%83%E5%B0%BC%E8%8C%A8%E5%85%AC%E5%BC%8F) * [Nilakantha 公式](https://scratch.mit.edu/projects/19839990/) * [勃興大神的 makefile](https://github.com/0140454/compute-pi/blob/5300af6db6e952b044a68f0aaa9ae2a501d36bf2/Makefile)