# 2016q3 Homework1 (compute-pi)
#### contributed by <`andy19950`>
### 執行 make check
```
/*----- baseline -----*/
16.23 user 0.00 system 0:16.23 elapsed 99% CPU
/*----- OpenMP 2 threads -----*/
16.21 user 0.00 system 0:08.11 elapsed 199% CPU
/*----- OpenMP 4 threads -----*/
17.70 user 0.00 system 0:04.75 elapsed 372% CPU
/*----- AVX SIMD -----*/
7.05 user 0.00 system 0:07.05 elapsed 99% CPU
/*----- AVX SIMD + loop unrolling-----*/
6.44 user 0.00 system 0:06.45 elapsed 99% CPU
```
- 可以發現使用 AVX SIMD 256bits暫存器來平行處理可以在執行時間上大幅縮短
---
### 使用 gnuplot 繪製執行結果
- 首先需要新增.gp檔,我從第一個作業那邊複製過來做修改。
- 執行 make gencsv 會產生 result_clock_gettime.csv 裡面有執行各個funcion的時間
- 因為 gnuplot 不能用逗號 "," 來做分隔所以必須修改 benchmark_clock_gettime.c
- Makefile也要稍做修改,之後就可以使用 using 每筆資料都把第一列當作 x軸,輸出當 y軸。
- 如此執行就可以畫出每筆資料的 迭代次數(X) 以及 執行時間(Y) 的比較圖。
- logscale 可以對某個軸取 log ,我對 x 軸取 log10 讓圖看起來比較好看。
``` clike=
set logscale x 10
plot "result_clock_gettime.csv" using 1:2 with lines title 'baseline', \
'' using 1:3 with lines title 'omp_2', \
'' using 1:4 with lines title 'omp_4', \
'' using 1:5 with lines title 'avx', \
'' using 1:6 with lines title 'avxunroll'
```
---
### 實做 Leibniz formula

- 其實用上面的式子可以很簡單的實做出來
```clike=
double compute_pi_Leibniz(size_t N)
{
double pi = 0.0;
double x;
for (size_t n = 0; n < N; n++) {
int temp = (n % 2) ? -1 : 1;
x = (double) temp / (2 * n + 1);
pi += x;
}
return pi * 4.0;
}
```
---
### 嘗試使用 AVX SIMD 來優化 Leibniz formula
- AVX 256 bits 暫存器為 ymm0~ymm15。
- AVX 指令為 _mm256_op_suffix
- op 我們有使用到 set, set1, add, mul, div, store
- suffix 是參數的型態,因為我們用doulbe來計算所以使用 packed double(pd)
- 因為使用 double 的關係 set 可以傳四個 doulbe (64bits) 參數把整個暫存器塞滿。
- 而 set1 則是傳一個 double 參數,暫存器的四個部份都儲存同一個參數。
```clike=
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);
ymm1 = _mm256_set1_pd(2.0);
ymm2 = _mm256_set1_pd(1.0);
ymm4 = _mm256_setzero_pd();
for (int i = 0; i <= N - 4; i += 4) {
ymm3 = _mm256_set_pd(i, i+1.0, i+2.0, i+3.0);
ymm3 = _mm256_mul_pd(ymm1,ymm3);
ymm3 = _mm256_add_pd(ymm3,ymm2);
ymm3 = _mm256_div_pd(ymm0,ymm3);
ymm4 = _mm256_add_pd(ymm3,ymm4);
}
double tmp[4] __attribute__((aligned(32)));
_mm256_store_pd(tmp, ymm4);
pi += tmp[0] + tmp[1] + tmp[2] + tmp[3];
return pi * 4.0;
}
```
- 因為要實現 (-1)^n^ 所以把 1 跟 -1 交錯存入ymm0
- 2*n + 1的部份用 ymm1, ymm2, ymm3來實做
- ymm4則當作答案的暫存器,最後使用 store 把 ymm4 存入記憶體 tmp 內
- 再把四個計算好的部份全部加起來 *4 就是答案 π !!
---
### 再使用 make check 跑跑看
```
/*----- Leibniz -----*/
N = 1000000000 , pi = 3.1415926546
7.61 user 0.00 system 0:07.61 elapsed 99% CPU
/*---- Leibniz + AVX SIMD -----*/
N = 1000000000 , pi = 3.1415926546
3.32 user 0.00 system 0:03.32 elapsed 100% CPU
```
- 結果證明萊布尼茲(Leibniz)的結論是對的。
- 而使用 AVX 來加速一樣有顯著的效果。
---
### 使用 gnuplot 把所有結果畫成圖表

---
### 其他估計 π 的方法
#### Nilakantha 級數 ----圖片來源:[wikiHow](http://zh.wikihow.com/%E8%AE%A1%E7%AE%97%E5%9C%86%E5%91%A8%E7%8E%87-Pi)

- 據說使用電腦運算分析後, Nilakantha 級數會比 Leibniz formula 算的更快更準!
- 有時間我會寫程式來印證看看他的說法。
---
### 結論
- 又把快遺忘的微積分拿出來複習一下,也發現很多方法可以來計算 π。
- gnuplot 又更會使用了,圖也畫的比較好看了。
- 使用電腦來做數學計算並驗證公式真的很有趣,也是我從來沒有想過的電腦使用方式。
- 本來前一個作業要用懂的 AVX SIMD 在這個作業終於搞懂他了。
---
### Reference
- [Intel Developer Zone](https://software.intel.com/en-us/node/524133)
- [同學A共筆](https://embedded2015.hackpad.com/ep/pad/static/0aZx9N8YlMi)
- [同學B共筆](https://hackmd.io/MYZgpgTAJmCsUFpYDNiwQFnMhBOAhmAGx5gYCMY+IUuRy5QA?view)
- [WikiPedia](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80)