contributed by < baimao8437
>
hunng
這個作業一開始我整個霧煞煞 沒了老溼的精美影片教學 頓失依靠
只好先讀神人們的共筆了解一些基本名詞與時間分析
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
#include <immintrin.h>
,並在 Makefile 中的編譯選項加入-mavx
測試原版程式效能:
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 建立的圖表(如下圖):
for(i=100; i<25000; i+=5000)
seq 1000 200 400000
跑跑看將近兩千筆資料這份報告之後可以拿來做研究所推甄、工作實習,甚至可作為論文的雛形,用與請精確 –jserv
Yes, Sir!baimao8437
畫出來後:
預料之中 跟原圖差距甚遠
連一點指數上升的感覺都沒有 無法理解
而且原圖是 Baseline > OpenMP_2 > AVX > OpenMP_4 > AVX_unrolling
我的結果是 Baseline > OpenMP_4 > OpenMP_2 > AVX > AVX_unrolling
好像差了有點多 不過參考了別人的圖發現有人跟我一樣結果 就安心多了
補上 gnuplot perfomance + error rate:
可看出圖表顯示有一些極端值造成線條的抖動
因此參考學長的筆記 試圖建立95信賴區間來排除極端值
機統沒學好 信賴區間忘光光: 簡單來說就這三句話
計算平均值
計算標準差(standard deviation)
平均值的正負標準差乘以 2 就是 95% 信賴區間
標準差長這樣:
然後 makefile 中加入 -lm
一樣讓他跑 2000 筆資料
結果:
補上 gnuplot:
證明:
首先考慮
積分後
接著證明收斂
兩邊積分後等於
當
得證!
接著寫成 code:
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 來實作:
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 但有了上面的範例就好懂多了 也比較容易寫出來
寫成 code 也不難 大概長這樣
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 優化的話 就要稍微細心一點了 改到頭昏腦脹
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;
}
我相信應該有更簡潔的寫法 但是至少這個結果是對的
來看圖表
效能來看最好的三個是 :
(時間少) AVX > AVX_unrolling > Leibniz_avx (時間多)
這個圖表看起來跟上面的 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 幾項 就幾乎等於