# 2017q1 Team9( Compute-pi )
contributed by<`yanang`><`donbader`>
###### tags: `yanang` `donbader`
[github-yanang](https://github.com/yanang/compute-pi) & [github-donbader](https://github.com/donbader/compute-pi) / [youtube](https://youtu.be/2o8dcBGWUQk)
## 作業要求
**比照 [B03: compute-pi](https://hackmd.io/s/HkiJpDPtx) 要求,特別留意測量時間的機制 (Wall-clock time vs. CPU time)**
- [x] 在 GitHub 上 fork [compute-pi](https://github.com/sysprog21/compute-pi),嘗試重現實驗,包含分析輸出
- [x] 注意: 要增加圓周率計算過程中迭代的數量,否則時間太短就看不出差異
- [x] 詳閱廖健富提供的[詳盡分析](https://hackpad.com/Hw1-Extcompute_pi-geXUeYjdv1I),研究 error rate 的計算,以及為何思考我們該留意後者
- [x] 用 [phonebok](/s/S1RVdgza) 提到的 gnuplot 作為 `$ make gencsv` 以外的輸出機制,預期執行 `$ make plot` 後,可透過 gnuplot 產生前述多種實做的效能分析比較圖表
- [x] 可善用 [raytracing](/s/B1W5AWza) 提到的 OpenMP, software pipelining, 以及 loop unrolling 一類的技巧來加速程式運作
- [x] 詳閱前方 "Wall-clock time vs. CPU time",思考如何精準計算時間
- [x] 除了研究程式以外,請證明 [Leibniz formula for π](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80),並且找出其他計算圓周率的方法
- [x] 請詳閱[許元杰的共筆](https://embedded2015.hackpad.com/-Ext1-Week-1-5rm31U3BOmh)
- [x] 將你的觀察、分析,以及各式效能改善過程,並善用 gnuplot 製圖,紀錄於「[作業區](https://hackmd.io/s/Hy2rieDYe)」
- [x] clock(), clock_gettime() 的使用
- [x] time(), gettimeofday() 的使用
- [x] 為什麼 clock_gettime() 結果飄忽不定?
- [x] 為什麼 time() 和 gettimeofday() 不適合拿來作 benchmark ?
## 實驗結果
### 分析各種公式
1. 當 error rate 收斂時 --- run 的次數
![](https://i.imgur.com/pUs8mIe.png)
2. 當 error rate 收斂時 --- run 的時間
![](https://i.imgur.com/6zg8W4m.png)
* 結論:
除了用硬體/軟體加速之外,我們更可以使用不同公式來改善速度
### 各種加速方法(以公式一離曼積分為例):
#### 1. Baseline (完全沒有加速)
```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;
}
```
#### 2. Openmp
* multi-thread
```c
double compute_pi_openmp(size_t N, int threads)
{
double pi = 0.0;
double dt = 1.0 / N;
double x;
#pragma omp parallel for num_threads(threads) 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;
}
```
比較 thread 數量
![](https://i.imgur.com/XpmmhaA.png)
* multi-thread simd
```c
double compute_pi_omp_simd(size_t N, int threads)
{
double pi = 0.0;
double dt = 1.0 / N;
double x;
#pragma omp parallel for simd num_threads(threads) 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;
}
```
比較 thread 數量
![](https://i.imgur.com/x8Jmyrn.png)
* **為什麼thread增加,效能卻沒有提升?**
* 當thread數量大於CPU的核心數時,更多的thread只會造成產生thread的overhead
* 越多的thread 代表要context switch的次數更多
* 當每個thread要搶奪共享的資源的時候,會互相等待
#### 3. AVX and AVX unrolling
```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;
}
```
#### 4. OpenCL
![](http://2.bp.blogspot.com/-_naxNAG6irc/UoIJuYxGe0I/AAAAAAAAByU/GuvNvSmEDv8/s1600/img6.png)
參考 [ierosodin 的共筆](https://hackmd.io/s/r1Yx6o5Kl#)
### 比較各種加速方法
* 繪圖方式1: `gnuplot with lines`
![](https://i.imgur.com/NfjCnkL.png)
* 繪圖方式2: 用 95% 信賴區間除雜訊
![](https://i.imgur.com/XJz2JVj.png)
* 繪圖方式3: 直接使用`gnuplot smooth bezier`
![](https://i.imgur.com/NKXgXfA.png)
## time libarary
* clock(void)
::: info
Linux man page :
The clock() function returns an approximation of processor time used by the program
:::
1. 在 32-bit 的系統中,CLOCKS_PER_SEC 約為 1000000,所以 clock() 每 72 分鐘將會回傳相似的值
2. 在某些實作中, clock() 可以藉由 wait() 包括子程序的時間。但是,在 linux 系統當中 clock() 仍然不包括等待中的子程序時間。
>linux man page :
>The **times()** function, which explicitly returns (separate) information about the caller and its children, may be **preferable**
* times(struct tms *buf)
:::info
linux man page :
times() returns the number of clock ticks that have elapsed since an arbitrary point in the past. The return value may overflow the possible range of type clock_t
:::
1. 回傳的時間
```c
struct tms {
clock_t tms_utime; /* user time */
clock_t tms_stime; /* system time */
clock_t tms_cutime; /* user time of children */
clock_t tms_cstime; /* system time of children */
}
```
2. 當子程序當中的 wait() 或是 waitpid() 回傳 process id 時,將其時間加入 times()
* clock_gettime(clockid_t clk_id, struct timespec *tp)
::: info
The functions clock_gettime() retrieve and set the time of the specified clock clk_id
:::
1. CLOCK_REALTIME:使用的是系統的掛鐘時間,當系統時間重置或改變,將影響結果
2. CLOCK_MONOTONIC:使用的是相對時間,通過 [jiffies](https://my.oschina.net/u/174242/blog/71851) 值來計算的。該時鐘不受系統時鐘源的影響,只受 jiffies 值的影響。
**[man clock_gettime()](https://linux.die.net/man/2/clock_gettime)**
* gettimeofday(struct timeval *tv, struct timezone *tz)
:::success
當 clock_gettime() clock_id 指定為 CLOCK_REALTIME 時,它與 gettimeofday 完全一樣
只不過它返回的是奈秒,而 gettimeofday 返回的是微秒
:::
- [ ] 為什麼 clock_gettime() 結果飄忽不定?
function的執行時間單位以 nanoseconds 來決定,執行時間很短時並無法排除 Linux 背景處理其他事情,導致額外的 cache miss ,所以需要很多針對某一筆參數作累積再平均,才能避免飄忽不定
- 繪圖方面可以使用以下方法去除雜訊:
- 95% 信賴區間
- smooth beizer(gnuplot)
- [ ] 為什麼 time() 和 gettimeofday() 不適合拿來作 benchmark ?
time() 和 gettimeofday() 未必是單調遞增的。
[NTP](https://zh.wikipedia.org/wiki/%E7%B6%B2%E8%B7%AF%E6%99%82%E9%96%93%E5%8D%94%E5%AE%9A) 會使得 wall-clock 加速或減速以對齊(drift)到正確的時間,當它在對齊時,抓到的時間並不能完全符合真實的時間流逝。
## 公式
### **黎曼積分**
$$
\begin{align}
\frac{\pi}{4} & =arctan(1)\\
\pi & = 4 \int_{0}^1 \frac{1}{1+x^2}dx\\
& = 4 \lim_{N\to \infty} \sum_{k=1}^N \frac{1}{1+(k/N)^2} \cdot \frac{1}{N}\\
& \approx 4 \sum_{k=1}^N \frac{1}{1+(k/N)^2} \cdot \frac{1}{N}
\end{align}
$$
> 在 MathJax 語法中可使用 &= 對齊式子,不過 hackmd 似乎不支援 [name=yanag][color=#83aacc]
> > 除了 &= 前後還須加上 begin{align} end{align} [name=yanag][color=#83aacc]
### **Lebiniz**
$$
\begin{align}
\pi&=\frac{4}{1}-\frac{4}{3}+\frac{4}{5}-\frac{4}{7}+\frac{4}{9}-\ldots\\
\pi&=4\sum_{n=0}^\infty\frac{(-1)^n}{2n+1}
\end{align}
$$
**pf :**
$$\begin{align}\tan(\frac{\pi}{4})&=1 ; \arctan(1)=\frac{\pi}{4};\\
\frac{pi}{4}&=\arctan(1)\\
&=\int_{0}^1 \frac{1}{1+x^2}dx\\&=\int_{0}^1 (\sum_{k=0}^n (-1)^kx^{2k}+\frac{(-1)^{n+1}\cdot x^{2n+2}}{1+x^2})dx\\&=(\sum_{k=0}^n \frac{(-1)^k}{2k+1})+(-1)^{n+1}(\int_{0}^1 \frac{x^{2n+2}}{1+x^2}dx)\end{align}\\as\ n \to \infty , 0\lt\int_{0}^1\frac{x^{2n+2}}{1+x^2}dx \lt \int_{0}^1 x^{2n+2}dx=\frac{1}{2n+3} \to 0 ;\\
藉由夾擠定理(Squeeze theorem) :\\
\frac{\pi}{4}=\int_{k=0}^\infty \frac{(-1)^k}{2k+1}$$
### **Wallis**
$$
\frac{\pi}{2}=\frac{2}{1}\cdot\frac{2}{3}\cdot\frac{4}{3}\cdot\frac{4}{5}\cdot\frac{6}{5}\cdot\frac{6}{7}\ldots\\
$$
**pf :**
by using [Euler's infinite product for sine](https://en.wikipedia.org/wiki/Euler_product#Examples)
$$\begin{align}
\frac{\sin(x)}{x}&=\prod_{n=1}^\infty (1-\frac{x^2}{n^2\pi^2})\\
let\ x &= \frac{\pi}{2}\\
\Rightarrow\frac{2}{\pi}&=\prod_{n=1}^\infty (1-\frac{1}{4n^2})\\
\Rightarrow\frac{\pi}{2}&=\prod_{n=1}^\infty (\frac{4n^2}{4n^2-1})\\
&=\prod_{n=1}^\infty (\frac{2n}{2n-1}\cdot\frac{2n}{2n+1})=\frac{2}{1}\cdot\frac{2}{3}\cdot\frac{4}{3}\cdot\frac{4}{5}\cdot\frac{6}{5}\cdot\frac{6}{7}\ldots
\end{align}$$
### **Nilakantha**
$$
\begin{align}
\pi&=3+\frac{4}{2\times3\times4}-\frac{4}{4\times5\times6}+\frac{4}{6\times7\times8}-\ldots\\
\pi&=3+\sum_{n=1}^\infty\frac{4\cdot(-1)^{n+1}}{2n\times(2n+1)\times(2n+2)}
\end{align}
$$
[**pf**](https://www.maa.org/sites/default/files/pdf/upload_library/22/Allendoerfer/1991/0025570x.di021167.02p0073q.pdf)
> Nilakantha 比 Leibniz 有更快的計算精準率,N=3 時 Leibniz(pi) = 2.8952... 而 Nilakantha(pi) = 3.1452... [name=wiki]
### **Chudnovsky**
$$
\begin{align}
\frac{1}{\pi}&=12\sum_{k=0}^\infty\frac{(-1)^k(6k)!(545140134k+13591409)}{(3k)!(k!)^3(640320)^{3k+3/2}}\\
\pi&=C(\sum_{k=0}^\infty\frac{M_k\cdot L_k}{X_k})^{-1},where\\
C&=426880\sqrt{10005}\\
M_k&=\frac{(6k)!}{(3k)!\times(k!)^3}\\
L_K&=(13591409+545140134\times k)\\
X_k&=(-640320)^{3k}=(-262537412640768000)^k
\end{align}
$$
[**pf**](https://paramanands.blogspot.tw/2013/06/proof-of-chudnovsky-series-for-1-by-pi.html#.WPUgeYiGOUk)
> 據 [function](https://www.ptt.cc/bbs/C_and_CPP/M.1379499525.A.BE5.html) 每多計算一位可增加14為有效小數
![](https://i.imgur.com/p410mDT.png)
## 尋找次數的方法
**方法一** : N = N+1 每次做 compute_pi(N) 都判斷 error rate 是否符合要求
```c
// judge function
int judge(int n) {
double pi = compute_pi_formula(formula_id, n);
double error = abs(pi - M_PI);
double error_diff = error - max_error;
return error_diff > 0 ? 1 : error_diff < 0 ? -1 : 0;
}
int n = 0;
while(judge(n) > 0 && ++n);
```
**方法二** : N = Nx10 ,達到 error rate 後退回前一個區間後,用 binary search 尋找答案
```c
int binary_search(int lower, int upper, int (*judge)(int))
{
int mid, decision;
while (lower < upper) {
mid = (upper + lower) / 2;
decision = (*judge)(mid);
if (decision > 0)
lower = mid + 1;
else if (decision < 0)
upper = mid - 1;
else
return mid;
}
return lower;
}
#define abs(num) (num) > 0 ? (num) : -(num)
int convergence(double max_error, int formula_id)
{
if (formula_id > MAX_FORMULA_ID || formula_id < 0) {
return -1;
}
// judge function
int judge(int n) {
double pi = compute_pi_formula(formula_id, n);
double error = abs(pi - M_PI);
double error_diff = error - max_error;
return error_diff > 0 ? 1 : error_diff < 0 ? -1 : 0;
}
long long N = 1;
while (judge(N) > 0)
N *= 10;
if (N == 1) return 1;
return binary_search(N / 10 + 1, N, judge) + 1;
}
```
## gnuplot
* result_clock_gettime.csv 中以 "," 切割各比資料
```
set datafile sep ','
```
* 以折線圖的方式繪製圖表
```
plot 'result_clock_gettime.csv' using 1:2 with linespoints \
linewidth 2 title 'baseline', \
```
* 以曲線圖的方式繪製圖表
```
plot 'result_clock_gettime.csv' using 1:2 smooth bezier \
linewidth 2 title 'baseline', \
```
* 以長條圖的方式繪製圖表
```
plot 'result_clock_gettime.csv' \
using 2:xtic(1) with histogram title 'baseline', \
```
## 參考資料
[Pi algorithms](https://en.wikipedia.org/wiki/Category:Pi_algorithms)
[GPGPU + SIMD](https://kheresy.wordpress.com/2013/11/29/openmp-4/)
[First OpenCL Program](https://www.fixstars.com/en/opencl/book/OpenCLProgrammingBook/first-opencl-program/)
[ierosodin 的共筆](https://hackmd.io/s/r1Yx6o5Kl#)
[Diana Ho 的共筆](https://hackmd.io/s/By25BnOp)
[why is clcock_gettime so erratic?](http://stackoverflow.com/questions/6814792/why-is-clock-gettime-so-erratic)
[gettimeofday() should never be used to measure time](https://blog.habets.se/2010/09/gettimeofday-should-never-be-used-to-measure-time.html)
[Why is threading increasing execution time ?](http://stackoverflow.com/questions/35573855/why-is-threading-increasing-execution-time-c-sharp)
[Richard's blog](http://wycwang.blogspot.tw/2013/11/opencl.html)
## 紀錄
5:00 Chudnovsky 證明
error rate: [gmp math (大數運算庫)](https://gmplib.org/pi-with-gmp.html)
改善 error rate計算