--- tags: ncku-course --- # 2016q3 Homework1 (compute-pi) ## 背景知識 在開始之前,先來釐清一些名詞。 ### Wall-clock time vs. CPU time * Wall-clock time 基本上是現實世界中實際經過的時間。 但有可能因為 NTP 或時區相關因素,導致不是單調遞增。 * CPU time 程式在 CPU 實際所花費的時間。 ### CPU bound vs. IO bound * CPU bound 主要的操作都是 CPU 運算,僅有少量的 IO 操作。這一類型的程式 ==有機會== 可以透過平行化的方式來加速。 * IO bound 與 CPU bound 相反,主要都是 IO 操作,無法受益於平行化處理。 ### Bourne shell 中的 time 為系統內建的工具之一,會計算程式執行的時間並回報給使用者。可以透過設定環境變數 `TIMEFORMAT` 來指定輸出格式。 預設而言,會輸出3個時間,分別是 `user`、`sys`、`real`。 * user time 在 user space 中花費的 CPU time。 * sys time 在 kernel space 中花費的 CPU time。 與 user time 相加即為 total CPU time。 * real time 也就是上方提及的 wall-clock time。不僅包含 CPU time,還包含 I/O 等等的時間。 ## 時間測量函數 * clock() 返回自程式開始執行到現在為止所使用的 CPU time,type 是 clock_t。若要換算成時間,則必須要除以 `CLOCKS_PER_SEC`。 除此之外,`clock()` 並沒有考慮到多執行緒的情況,因此會有誤差。 * clock_gettime() Prototype: ```clike= int clock_gettime(clockid_t clk_id, struct timespec *tp); ``` 精準度可以到達奈秒等級。 根據函數的第一個參數,可以決定要取得的來源,若是傳入 `CLOCK_REALTIME` 則可能出現非單調遞增的情況,此時要用 `CLOCK_MONOTONIC` 或 `CLOCK_MONOTONIC_RAW` 才對。 ```clike= struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ }; ``` * time() 回傳自 1970年01月01日 以來所經過的秒數。 * gettimeofday() 取得目前的時間,精確度可到微秒等級。但跟 `time()` 一樣不一定是單調遞增,所以也不常用來量測時間。 ```clike= struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ }; ``` ## Baseline 效能 執行 `make check` 得到以下結果。 另外,在 Makefile 中使用的 `time` 是 `/usr/bin/time`,而不是 bash 內建的 time。所以在輸出格式上才會有所差異。 ```shell= $ make check time ./time_test_baseline N = 400000000 , pi = 3.141593 3.32user 0.04system 0:03.37elapsed 99%CPU (0avgtext+0avgdata 1752maxresident)k 0inputs+0outputs (0major+86minor)pagefaults 0swaps time ./time_test_openmp_2 N = 400000000 , pi = 3.141593 3.72user 0.00system 0:01.86elapsed 199%CPU (0avgtext+0avgdata 1724maxresident)k 0inputs+0outputs (0major+89minor)pagefaults 0swaps time ./time_test_openmp_4 N = 400000000 , pi = 3.141593 6.63user 0.01system 0:01.71elapsed 388%CPU (0avgtext+0avgdata 1820maxresident)k 0inputs+0outputs (0major+92minor)pagefaults 0swaps time ./time_test_avx N = 400000000 , pi = 3.141593 0.97user 0.01system 0:00.98elapsed 100%CPU (0avgtext+0avgdata 1712maxresident)k 0inputs+0outputs (0major+86minor)pagefaults 0swaps time ./time_test_avxunroll N = 400000000 , pi = 3.141593 0.94user 0.01system 0:00.95elapsed 100%CPU (0avgtext+0avgdata 1704maxresident)k 0inputs+0outputs (0major+86minor)pagefaults 0swaps ``` 觀察 `time` 輸出的時間可以知道 * 除了 OpenMP 以外的時間都是 user time ≒ real time。 表示他們屬於 CPU bound,但無法受益於平行化處理。 * 而 OpenMP 則是 user time > real time。 表示他們是屬於可以受益於平行化處理的 CPU bound。 * OpenMP 的部份有 CPU utilization 超過 100% 的情況。 CPU utilization 100% 的時候表示充份地利用了一顆核心,以我的筆電為例,有四顆核心,最高只會出現 400%。 再來是利用 LibreOffice 畫圖 ![](https://i.imgur.com/KXJ5h0G.png) 每次都還要開 LibreOffice 才能生成圖片,有點慢。依照老師說的,用 gnuplot 來完成這一切,只要下個 `make plot` 就好。 ![](https://i.imgur.com/QbGusNZ.png) ## Pi 的計算方式 ### Baseline 版本 ```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; } ``` 因為 $\int_{0}^{1} \frac{1}{x^2 + 1} dt = tan^{-1}1 - tan^{-1}0 = \frac{\pi}{4}$,所以算出來要乘以4倍。 ### Leibniz 版本 #### 公式 $$ \frac{\pi}{4} = \sum_{n=0}^{\infty} \frac{(-1)^{n}}{2n+1} $$ #### 證明 直接相除可得 $$ \frac{1}{x^2 + 1} = 1 - x^2 + x^4 - \dots + (-1)^{n}x^{2n} + \frac{(-1)^{n+1}x^{2n+2}}{x^{2}+1} $$ 接著對等號兩邊做定積分得 $$ \frac{\pi}{4} = 1 - \frac{1}{3} + \frac{1}{5} - \dots + \frac{(-1)^{n}}{2n+1} + (-1)^{n+1} \int_{0}^{1} \frac{x^{2n+2}}{x^{2}+1} dx $$ 最後,當 $n \to \infty$ 時,可以得知最後一項的積分會趨近於 0 $$ \int_{0}^{1} \frac{x^{2n+2}}{x^{2}+1} dx < \int_{0}^{1} x^{2n+2} dx = \frac{1}{2n+3} \to 0 $$ 至此,即可得證 $\frac{\pi}{4} = \sum_{n=0}^{\infty} \frac{(-1)^{n}}{2n+1}$ #### 執行結果 ```shell= $ make check time ./time_test_leibniz N = 400000000 , pi = 3.141593 1.68user 0.00system 0:01.68elapsed 100%CPU (0avgtext+0avgdata 1796maxresident)k 0inputs+0outputs (0major+83minor)pagefaults 0swaps time ./time_test_leibniz_openmp_2 N = 400000000 , pi = 3.141593 1.70user 0.00system 0:00.85elapsed 199%CPU (0avgtext+0avgdata 1788maxresident)k 0inputs+0outputs (0major+90minor)pagefaults 0swaps time ./time_test_leibniz_openmp_4 N = 400000000 , pi = 3.141593 3.36user 0.00system 0:00.85elapsed 392%CPU (0avgtext+0avgdata 1740maxresident)k 0inputs+0outputs (0major+91minor)pagefaults 0swaps time ./time_test_leibniz_avx N = 400000000 , pi = 3.141593 1.02user 0.00system 0:01.02elapsed 99%CPU (0avgtext+0avgdata 1936maxresident)k 0inputs+0outputs (0major+87minor)pagefaults 0swaps time ./time_test_leibniz_avxunroll N = 400000000 , pi = 3.141593 1.29user 0.00system 0:01.29elapsed 99%CPU (0avgtext+0avgdata 1784maxresident)k 0inputs+0outputs (0major+87minor)pagefaults 0swaps ``` ![](https://i.imgur.com/lDhgjj5.png) 讓我出乎意料的是 AVX 並沒有像 Baseline 版本那樣,比 OpenMP 快,反而變慢了... ### Euler 版本 #### 公式 $$ \frac{\pi^{2}}{6} = \frac{1}{1^{2}} + \frac{1}{2^{2}} + \frac{1}{3^{2}} + \dots $$ #### 執行結果 ```shell= $ make check time ./time_test_euler N = 400000000 , pi = 3.141593 1.66user 0.00system 0:01.67elapsed 99%CPU (0avgtext+0avgdata 1896maxresident)k 0inputs+0outputs (0major+92minor)pagefaults 0swaps time ./time_test_euler_openmp_2 N = 400000000 , pi = 3.141593 1.70user 0.00system 0:00.85elapsed 199%CPU (0avgtext+0avgdata 2144maxresident)k 0inputs+0outputs (0major+95minor)pagefaults 0swaps time ./time_test_euler_openmp_4 N = 400000000 , pi = 3.141593 3.38user 0.00system 0:00.85elapsed 396%CPU (0avgtext+0avgdata 2008maxresident)k 0inputs+0outputs (0major+96minor)pagefaults 0swaps time ./time_test_euler_avx N = 400000000 , pi = 3.141593 1.02user 0.00system 0:01.02elapsed 99%CPU (0avgtext+0avgdata 2092maxresident)k 0inputs+0outputs (0major+95minor)pagefaults 0swaps time ./time_test_euler_avxunroll N = 400000000 , pi = 3.141593 1.29user 0.00system 0:01.29elapsed 99%CPU (0avgtext+0avgdata 2104maxresident)k 0inputs+0outputs (0major+94minor)pagefaults 0swaps ``` ![](https://i.imgur.com/YSaYuUb.png) AVX 的部份也是比 OpenMP 還要慢。 ## Error Rate ### 比較不同版本的 Error Rate ![](https://i.imgur.com/i1Nm2EM.png) Baseline 的 error rate 與 Leibniz 的重疊了,而從圖中可以看到 Euler 的方法在大部份的時候,精準度都是比其他兩個好一點點。 ### 同版本下 AVX Error Rate 與眾不同的問題 看到老師提及[賴劭芊同學的HackMD](https://hackmd.io/s/HJrLU0dp)以及[王紹華同學的HackMD](https://hackmd.io/KYQwjARgZgTArAZgLQgJwAYpICyoMYAmSAHJiEggGzzqQEIDsqYQA===)才發現AVX的問題,只要將 Makefile 中 for 迴圈的資料點改為可被16整除即可修正。 將 ```shell= for i in `seq 1000 5000 1000000`; do ``` 改成 ```shell= for i in `seq 1008 4000 1000000`; do ``` 原先是 ![](https://i.imgur.com/oAmhhY6.png) 修改後變成 ![](https://i.imgur.com/cbc6vDZ.png) ## 參考資料 * [Leibniz formula for π - Wikipedia](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80) * [圓周率 - 維基百科](https://zh.wikipedia.org/wiki/%E5%9C%93%E5%91%A8%E7%8E%87#.E6.95.B8.E5.AD.B8.E5.88.86.E6.9E.90) * [ettimeofday(2) - Linux manual page](http://man7.org/linux/man-pages/man2/gettimeofday.2.html) * [clock_getres(2) - Linux manual page](http://man7.org/linux/man-pages/man2/clock_gettime.2.html) * [廖健富學長的Hackpad](https://hackpad.com/Hw1-Extcompute_pi-geXUeYjdv1I) * [TempoJiJi的HackMD](https://hackmd.io/s/Hy7tssw6)