contributed by <krimson8
, aben20807
>
原始程式碼: assign_5_18
解說錄影
linux-tools-generic
後面的 "uname -r" 表示 Linux 核心的版本號,這是因為 Linux 工具依賴特定的核心版本,請留意1,10: 1.557728
(spliting: 1, unrolling: 10, CPE: 1.557728)username
>請協助張貼你的測試結果
jserv
>jserv
>krimson8
>aben20807
>aben20807
>PC-65304
>siahuat0727
>popo
>jkrvivian
>LuisHsu
>MetalheadKen
>Joey Wang
>wingard
>Dino Lai
>dange0
>allenchen8210
>jerryzj
>tigercosmos
>Page 345
Cycles Per Element 的 element 指的是對一個單位操作平均使用的 cycles
函式整體 cycles = CPE n + overhead
更直觀的解釋:
輸入 個元素,平均每個元素獲得的 cycles 就接近 CPE ( 夠大時可省略 overhead)
Page 395 (國際版的第 609 頁)
在練習題 5.5 和 5.6 中我們考慮了多項式求值的任務,既有直接求值,也有用 Horner 方法求值。試著用我們講過的優化技術寫出這個函數更快的版本,這些技術包括循環展開、並行累積和重新組合。你會發現有很多不同的方法可以將 Horner 方法和直接求值與這些優化技術混合起來。理想狀況下,你能達到的 CPE 應該接近於你的機器的吞吐量界限。我們最佳版本在參考機上能使 CPE 達到 1.07。
Page 365
假設寫一個多項式求值的函數,這裡,多項式的次數為 ,係數為 , , … , 。對於值 ,我們對多項式求值,計算
這個求值可以用下面的函數來實作,參數包括一個係數數組 、值 和多項式的次數 degree
(等式 (5.2) 中的值 )。在這個函數的一個循環中,我們計算連續的等式的項,及連續的 的冪:
Page 366
我們繼續探索練習題 5.5 中描述的多項式求值的方法。通過採用 Horner 法 (以英國數學家 William G. Horner (1786 - 1837) 命名) 對多項式求值,我們可減少乘法的數量。其思想是反覆提出 的冪,得到下面的求值:
使用 Horner 法,我們可以用下面的程式碼實作多項式求值:
PAPI_get_real_cyc()
libpapi-dev
才能使用
PAPI_get_real_cyc()
使用獨立計算時間的方式 CLOCK_GETTIME
,然後在固定頻率的情況下跑測試程式,則 程式碼運行時間 × CPU 頻率 = CYCLE 數
以最高頻率執行的方式 (利用 cpupower),來達到相對準確的 cycle 數,因此我們計算 cycle 的方式為:在執行時以最大頻率執行並利用 clock_gettime()
得到時間,讀取 Linux 中 /proc
所記錄的最大頻率,相乘及得。
上面提到所使用的 PAPI
的計算原理可見上方
至於 RDTSC 的操作原理可見 Intel® 64 and IA-32 Architectures Software Developer’s Manual 的第 3309 頁的 17-17 章節
Processor families increment the time-stamp counter differently:
- …the time-stamp counter increments with every internal processor clock cycle.
The internal processor clock cycle is determined by the current core-clock to bus-clock ratio.- … the time-stamp counter increments at a constant rate. That rate may be set by the
maximum core-clock to bus-clock ratio of the processor or may be set by the maximum resolved frequency at
which the processor is booted. The maximum resolved frequency may differ from the processor base
frequency, …
簡單來說,在 Pentium 4
之前的處理器,TSC
暫存器裡面的內容是會根據每個 cycle 來進行 increment 的,但 Pentium 4
之後的處理器因多核的原因而改成使用 constant rate,而且這個 constant rate 會依頻率而浮動,因此出現下圖的狀況
比方說,在筆電 intel U 系列處理器上,往往有着較低的基底 (base) 頻率,和很高的加速 (burst) 頻率,這樣設計的目的是當計算需求低的時候可以使用低頻率,然後當處理器探測到需要高計算力時便開始增加頻率,於是便出現上圖的穩定增加 cycle ,然後 cycle 開始快速降落,再來繼續穩定提升
因此爲了穩定的計算準確的 CPE,我們設計如下 實驗環境與條件
首先附上硬體環境,其中請注意 model name
項目,此處理器的基底頻率爲 1.60 GHz
在執行上述程式碼的時候,使用二個 CPU affinity 相關工具來達到最佳效果
具體實作方式如下(需要 root 權限)
-c 0
的意思是讓程式只能在 cpu 0 上面運作,不會切換到其他CPU 核
接下來,我們需要完成避免對 TSC
的依賴
具體做法: 鎖定CPU 頻率 使用別的時間計算方式 時間 頻率 得到 CYCLE
鎖定 CPU 頻率
需要更改系統設定與開機變數,有一定風險,請務必在嘗試前備份資料
首先使用的工具是 cpupower,接下來的 所有operation 都需要使用 root 權限
第一步,確認 CPU driver
可以不用 grep
,輸出結果如下
可以看到結果是 intel_pstate
,關於這個裝置驅動程式的詳細描述可以看 這裏,其中重點是
There are two P-state selection algorithms provided by intel_pstate in the active mode: powersave and performance. The way they both operate depends on whether or not the hardware-managed P-states (HWP) feature has been enabled in the processor and possibly on the processor model.
上述內容敘述的是 intel_pstate
這個裝置驅動程式僅有兩種 governor:performance
和 powersave
。governor 的意思可以理解成 profile,意思說不同 profile 有着不同的效能設定,而最重要的是 我們並無法去對這兩個 governor 進行調整與設定
第二步,更換 CPU driver
有兩種方法,分別是 temporary 和 permanent 的方法
e
linux
的命令,在末端(引號內)加入如下字串
範例:
intel_pstate=disable
字串拿掉,再執行 update-grub
即可更換完畢並且從新開機後,cpupower frequency-info | grep driver
的輸出變爲
也可以使用 cat /proc/cmdline
來查看 boot 參數
第三步,切換 governor
執行 cpupower -c 0 frequency-info
可得到如下結果(-c 0 爲指定觀察 cpu 0)
我們可以看到 available cpufreq governors
那一欄出現跟之前不同的選項,而我們允許更改的是 userspace
governor 選項,若此選項沒有出現請執行下列指令
之後更換使用的 governor
第四步,更改 CPU 的頻率
目前測試錯誤
執行如下命令
可用 GHz 或 kHz,注意所設訂的頻率還需要符合 available frequency steps
內的頻率不能胡亂設定
設定好後可以用下列的命令觀察 CPU 頻率
第五步 執行結果
經實驗,動態鏈接 .o 檔執行程式會有顯著的效能負面影響,繼續研究中
因爲不同數量的 unrolling 和 splitting 的程式碼長得是非常類似,因此我們討論過後撰寫了以下這個程式執行流程,希望大家可以來幫助我們執行程式收集實驗數據
test_poly
)system()
另建立一個行程呼叫 gcc ,再用 dlopen()
把已編譯的 .o
檔動態鏈結,主程式就能使用發現有沒有清空 cache (每次迴圈) 並不影響,但清空 cache 需要花更多時間執行整個程式,因此排除 cache 影響
sudo ./test_poly
)
或者直接
兩次 free
命令來觀察釋放的記憶體附上清除記憶體前與清除後的結果對比圖:
5.5
vs. 5.6
CS:APP 5.18 的作業要求是將 5.5
和 5.6
節計算一元多次方程式的程式碼進行最佳化,將分別原本是 CPE 5(5.5)和 CPE 8(5.6)的程式碼極限最佳化,課本給出的參考 CPE 是 1.07 左右,而課本所使用的爲 第四代 intel core i7
接下來因為函式命名關係,我們統一和 CSAPP 5.5
的比較 CPE (圖片斜率) 並將其命名為 (1, 1):一個 splitting, 一個 unrolling,其程式碼因為自動產生的原因所以變成以下的型式,但是經過測試因為 overhead 較小所以幾乎不影響與最上方 5.5
的結果。
首先我們嘗試 Loop Splitting 的最佳化,做法可參閱課程共筆 CS:APP 第 5 章重點提示和練習。
程式碼的部分嘗試 split 成 2 個到 8 個變數分別進行計算,最後再相加。
以下程式碼分成 4 個變數
此段程式碼相比原本 (1, 1) 的 cycle 數少 2 倍多:
接下來嘗試的是 Loop Unrolling,根據共筆的提示,將 array 的 load 以三次爲單位作爲一整個 operation,詳見下方程式碼
這樣的做法其實就是 5.6
節提到的 horner method 的變形
其結果比純粹使用 4 個 splitting 還快:
再根據上方的程式碼,嘗試進行 Loop Splitting,即結合兩種方法,以下程式碼實作 2 個 splitting 同時 3 個 unrolling
這個型式也是網際網路上常見的解答
比較如下圖:
可以看出 (2, 3) 的 CPE 已經相當接近 1
aben20807
) 為例
時間 x 最高頻率
,所以有很大的誤差,目前以接上電源解決,如下圖: