# 2017 q1 Homework1 (compute-pi) contributed by <`zmke`> ### Reviewed by <`rayleigh0407`> * 建議可以證明一下 Leibniz formula ### Reviewed by <`twzjwang`> - 執行時同時使用其他程式,為什麼 omp_4 受到的影響最大? - 可以探討作業要求中時間函式部分提到的問題、延伸思考 ## 開發環境 OS: Ubuntu 16.04 LTS Architecture: x86_64 CPU 作業模式: 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 Model name: Intel(R) Core(TM) i5-4200H CPU @ 2.80GHz L1d 快取: 32K L1i 快取: 32K L2 快取: 256K L3 快取: 3072K ## AVX * Advanced Vector Extensions * Intel 開發的 Single Instruction Multiple Data 指令集,在一個 cycle 內可以同時對多個數據執行同樣的指令 * ymm0~ymm15 共 16 個暫存器,每個暫存器內可以放256 bits * e.g. 8個 32-bit float; 4個 64-bit double ## 時間處理與 time 函式使用 * NTP (Network Time Protocol) * 透過網路封包交換,將電腦之間的時鐘校正和同步 * clock() * return CPU time * 回傳值是 `clock_t` type ,要除以 `CLOCKS_PER_SEC` 才能取得以秒為單位的時間 * On a 32-bit system where CLOCKS_PER_SEC equals 1000000 this function will return the same value approximately every 72 minutes. ( 超過72分鐘會 wrap around ) * 在 muti threads 的情況下, CPU time 是每條 thread 的加總 * clock_gettime() * `int clock_gettime(clockid_t clk_id, struct timespec *tp);` * retrieve and set 的類型由 clk_id 決定 * CLOCK_REALTIME : system-wide clok ,會回傳 real time 也就是 wall-clock ,如果使用者任意更動系統時間,會影響測量結果 * CLOCK_MONOTONIC : 不受 discontinuous jumps 影響 * 精度為 nanoseconds * time() * wall-clock * return 1970-01-01 00:00:00 +0000 (UTC) 到目前為止的時間,精度為 seconds 以前寫作業的時候曾經遇過 multi thread 時間測量的問題,一開始是用 clock() ,發現測出來的時間跟預期相差很大,現在終於知道原因了 ## 原始版本 * 修改 Makefile ```== gencsv: default for i in `seq 500 500 250000`; do \ printf "%d," $$i;\ ./benchmark_clock_gettime $$i; \ done > result_clock_gettime.csv plot: gencsv gnuplot script.gp ``` 從 N=500 開始,間隔 500 測試到 N=250000 `$ make plot` ![](https://i.imgur.com/ljIKv1M.png) 起伏有點誇張,尤其是 omp_4,發現可能的原因是我邊等輸出邊用 youtube 聽音樂,把網頁關起來再試一次 ![](https://i.imgur.com/yt3aP1d.png) 這次在等輸出的時候沒有對電腦進行任何操作,明顯比上一次穩定很多 ## Leibniz ![](https://i.imgur.com/UFp5hnk.png) ``` == double compute_pi_leibniz(size_t N){ double pi = 0.0; for(size_t i = 0; i < N; i++) pi += pow(-1.0, i) / (2*i + 1); return pi * 4.0; } ``` omp 版本 ``` == double compute_pi_leibniz_openmp(size_t N, int threads){ double pi = 0.0; #pragma omp parallel num_threads(threads) { #pragma omp for reduction(+:pi) for(size_t i = 0; i < N; i++) pi += pow(-1.0, i) / (2*i + 1); } return pi * 4.0; } ``` `$ make plot` ![](https://i.imgur.com/Yd1v6LM.png) 發現 Leibniz 求圓周率明顯比其他方法慢,猜測是 `pow(-1.0, i)` 太浪費時間,修改版本如下 ```== double compute_pi_leibniz(size_t N){ double pi = 0.0; for(size_t i = 0; i < N; i++){ pi += (i % 2) ? (-1.0 / (2*i + 1)) : (1.0 / (2*i + 1)); } return pi * 4.0; } ``` omp ```== double compute_pi_leibniz_openmp(size_t N, int threads){ double pi = 0.0; #pragma omp parallel num_threads(threads) { #pragma omp for reduction(+:pi) for(size_t i = 0; i < N; i++) pi += (i % 2) ? (-1.0 / (2*i + 1)) : (1.0 / (2*i + 1)); } return pi * 4.0; } ``` 模仿原本的 avx,練習把 Leibniz 改成用 AVX 計算 ```== 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); //pow(-1.0, i) ymm1 = _mm256_set1_pd(2.0); ymm2 = _mm256_set1_pd(1.0); ymm4 = _mm256_setzero_pd(); //sum of pi 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(ymm3, ymm1); //2*i ymm3 = _mm256_add_pd(ymm3, ymm2); //2*i+1 ymm3 = _mm256_div_pd(ymm0, ymm3); //pow(-1.0, i) / (2*i+1) ymm4 = _mm256_add_pd(ymm4, ymm3); //pi += pow(-1.0, i) / (2*i+1) } double tmp[4] __attribute__((aligned(32))); _mm256_store_pd(tmp, ymm4); pi += tmp[0] + tmp[1] + tmp[2] + tmp[3]; return pi * 4.0; } ``` `$ make plot` ![](https://i.imgur.com/kE38GWY.png) 發現把 `pow(-1.0, i)` 改掉之後, leibinz_omp4 變成執行速度最快的版本 ## Compute Error 以 arccos(-1) 為基準計算誤差 ```== // Baseline result = compute_pi_baseline(N); error = (result - PI > 0) ? result - PI : PI - result; printf("%lf,", error); // Leibniz result = compute_pi_leibniz(N); error = (result - PI > 0) ? result - PI : PI - result; printf("%lf\n", error); ``` `$ make plot_error` ![](https://i.imgur.com/JV6CsAM.png) 兩條線幾乎重疊,表示在同樣的 N 精度差距不大 把判斷正負的判斷式移除後再畫一次,發現兩個求圓周率方法分別是從上下逼近 `$ make plot_error` ![](https://i.imgur.com/qcmVRWL.png) ## Reference [AVX是什麼?AVX指令集技術與應用解析](http://www.expreview.com/13236.html) [TempoJiJi 筆記](https://hackmd.io/s/Hy7tssw6) [clock()](https://linux.die.net/man/3/clock) [clock_gettime()](https://linux.die.net/man/2/clock_gettime) [NTP](http://linux.vbird.org/linux_server/0440ntp.php) [Leibniz formula for pi](https://en.wikipedia.org/wiki/Leibniz_formula_for_%CF%80)