contributed by < yceugene
>
linux2020
sysprog2020
final project
題目: 參照 Assign 5.13,進行 CS:APP 第 5 章作業
5.13
(Page 393),要有可執行的驗證環境
參考前一分學員的 Github 實作: allenchen8210
實驗環境:
預期進行方式:
inner()
,搭配不同 datatype (integer、float) 比較程式的 CPE 差異。<time.h>
計算 CPE 並轉成圖像時,會有異常點的問題。inner()
。CS:APP Homework Problems 5.13 題目原文
題意: inner4
是在計算兩向量 u
、 v
的內積,以不同的資料型態 (integer
或 floating-point
),在 x86-64
下執行這段程式,其 CPE
約為 14~18
。比照第五章 combine 1~4 的改良方式,修改 inner4
程式碼:
inner4
程式碼:
書上的實測結果:
考慮 datatype 為 double,inner4
的 inner loop 對應的 x86-64 assembly code 如下:
動作流程:
functional units
A. 比照 Figures 5.13, 5.14,描繪出程式碼的 instruction sequence 如何運作以及資料相依性問題。參考來源1, 參考來源2
B. 當資料型別為 double,程式碼 critical path 的 CPE lower bound 為何? 參考來源
The lower bound is the delay limit of floating-point addition, and the CPE is 3.00.
C. 當資料型別為 integer,程式碼 critical path 的 CPE lower bound 為何? 參考來源
Delay limit for integer addition, CPE is 1.00
D. 解釋為何當資料型別為 floating-point 時,CPE 為 3.00 (multiplication operation requires 5 clock cycles)? 參考來源
There is only floating point addition on the critical path.
inner()
如下inner1
inner2
改善 inner1 中每次 iteration 都要重取 vec_length 的問題。
inner3
改善 inner2 中每次 iteration 都要重取 vector element 的問題。
inner4
改善 inner3 中每次 iteration 都要透過 pointer 寫入計算結果的問題,改成全部計算完再寫入。
performancelab 主要是利用 rdtsc
指令去讀取 clock cycle 個數。
將 performancelab 的 clock.c 及 fcyc.c 導入後, 以 COMPENSATE=1
執行的結果可得的 CPE 分別是 21, 16, 10 及 6. gnuplot 執行 output cycle 的結果可得到下圖:
datatype: integer
datatype: float-point
CPE 測量結果:
將 performancelab 的 clock.c 及 fcyc.c 導入後, 以 COMPENSATE=0
執行的結果可得的 CPE 分別是 21, 16, 10 及 6. gnuplot 執行 output cycle 的結果可得到下圖:
datatype: integer
datatype: float-point
CPE 測量結果:
另外發現在 COMPENSATE=1 時, 有時會得到上圖. 這是很糟糕的結果. 經查發現 performancelab clock.c 有一段程式碼如下:
line #9, ctime=time - ticks*cyc_per_tick, 這會造成 ctime < 0, 以至於出現 1.8x 的奇怪 cycle. 看了一下程式碼, 修改一下如下:
也許這不是正解, 關鍵在於 rdtsc 與 time() 之間的修正應如何進行? 執行起來如下圖, 感覺好多了!
為了測試方便, 先將 line #11, 改為 10: unsigned long dimRange = 100;
$ gcc -g -o w75_13 w75_13.c
兩個長度為 1000000 的 integer vector 計算內積, 不同 inner() 花費時間差異
$ valgrind –tool=callgrind -v ./5_13
執行完會輸出 callgrind.out.#### 檔案 ("####" 是 pid). 這個檔案需使用 KCachegrind 來讀取. 在安裝好(安裝如下)後, 在 callgrind.out.#### 相同目錄下輸入 kcachegrind 即可.
- KCachegrind install:
$ sudo apt-get update -y
$ sudo apt-get install -y kcachegrind
kcachegrind 會讀取 callgrind.out.#### 並顯示出分析結果:
觀察測量結果,不同 inner function 佔整體程式執行時間的比例如下:
* inner1: 37.77%
* inner2: 33.36%
* inner3: 9.81%
* inner4: 7.85%
perf operation 需要 root 權限, sudo is required for most command:
安裝 perf:
$ sudo apt install linux-tools-$(uname -r) linux-tools-generic
check installation:
$ which perf
設定系統權限 (更改 kernel 權限, 以便讀取 perf 及 CPU 資料):
使用前須修改程式碼以取得 pid, printf("pid:%d\n", getpid()); 並加上 delay (sleep(10)) 來爭取 perf 執行時間。
觀察測量結果,不同 inner function 佔整體程式執行時間的比例如下:
* inner1: 23.08%
* inner2: 21.37%
* inner3: 7.69%
* inner4: 5.98%
flamegraph.pl generate svg file
$ ./flamegraph.pl perf.folded > perf.svg
perf svg graph
原先自己預期的目標是把題目給定的 double data type 組合語言,整合到 inner4 的 C 程式碼中,再分析效能變化,但因為對 assembly 和 asm 呼叫方式還不夠熟悉,實做上有些瓶頸還在克服中。
為了更好的測量 clock cycle, 嘗試將 CPU clock speed 設成 constant。(參考: Control Intel CPUs energy performance policy)
cpupower
查看 CPU 時脈資訊, 主要是了解目前 CPU 可選擇的時脈範圍 (如下資訊可知,我們的 CPU 時脈範圍是: 800 MHz ~ 4.40 GHz):
Linux-CPUFreq governors
),這裡將 CPU 時脈設為常數 4300MHz
。指令可參考 cpufreq的五種模式。
4300MHz
接著嘗試只使用一個特定 CPU core (CPU:1) 來執行程式,這是為了在多核心處理器上更精確的量測程式所需的 clock cycle。 (參考: Processor affinity)
sched_setaffinity()
(參考: sched_setaffinity)執行結果
datatype: Integer
datatype: Floating-point
量測到的 CPE 數值約為:
CPE (限制只使用 CPU1) | inner1 | inner2 | inner3 | inner4 |
---|---|---|---|---|
Integer | 16.04 | 12.16 | 8.54 | 4.78 |
Floating-point | 16.67 | 12.90 | 9.60 | 6.71 |
CPE (未限制 Processor affinity) | inner1 | inner2 | inner3 | inner4 |
---|---|---|---|---|
Integer | 16.32 | 12.03 | 8.43 | 5.02 |
Floating-point | 16.70 | 12.26 | 9.54 | 9.52 |
分析: 對照上述兩分表格可發現,有綁定 CPU-core 的程式,其所計算的 clock cycle 數較小,特別是 inner4 floating point 的部分,差異又更是明顯。