contributed by < haogroot
>
linux2020
Kernel version: 5.3.0-40-generic
OS: Ubuntu 19.10
CPU model: Intel® Core™ i7-8565U CPU @ 1.80GHz
lsmod
的輸出結果有一欄名為 Used by
,這是 "each module's use count and a list of referring modules",但如何實作出來呢?模組間的相依性和實際使用次數 (reference counting) 在 Linux 核心如何追蹤呢?fibdrv.c
存在著 DEFINE_MUTEX
, mutex_trylock
, mutex_init
, mutex_unlock
, mutex_destroy
等字樣,什麼場景中會需要呢?撰寫多執行緒的 userspace 程式來測試,觀察 Linux 核心模組若沒用到 mutex,到底會發生什麼問題在 kernel module 中透過 ktime_get()
來量測計算所需時間,在 user space 透過 clock_gettime()
來測量。
由於 read 預設是回傳 ssize_t
,只有 8 bytes,我們無法回傳 fib_sequence()
執行時間與大數計算的結果,改為將結果寫入 fib_read()
的參數 char *buf
,在 kernel space 宣告一個陣列 kbuffer 來儲存,將 ktime 所計算的時間寫到前八個 bytes,大數計算的結果寫到接下來的位置,最後透過 copy_to_user()
將 kernel space 的資料傳到 user space。
用字要精準,到底是 還是 ,另外 overflow 的空間範圍要說清楚
當 fib(n) 中的 n ≥ 93 會發生 overflow ( fib(93)=12,200,160,415,121,876,738,但 unsigned long long 可表達最大為 18,446,744,073,709,551,615),先限制測試到 92。測時結果如下圖所示,隨著測試數字愈大,在 kernel space 耗費時間也跟著增加,從 user space 跟 kernel space 之間的差距為 200 ms,可推估在 kernel 計算完傳輸到 user space 耗費約 200 ms。
在你所不知道的C語言:遞迴呼運算叫篇 探討過 Fibonacci sequence 不同實作演算法的複雜度,如下表所示:
方法 | 複雜度 |
---|---|
Recursive | |
Iterative | |
Tail Recursion | |
Q-Matrix | |
Fast Doubling |
當 fib(n) 中的 n 大於 93 會發生 overflow,透過大數運算來解決這個問題。
參考 tiny-bignum-c 來實作大數運算,透過 struct bn
來存放大數運算的結果,每一個 element 大小為 32 bits,最多可以用來代表 (0xFFFFFFFF
)。
isolcpus=1
,reboot 後透過 cat /proc/cmdline
來驗證。$ sudo sh -c "echo 1 > /sys/devices/system/cpu/intel_pstate/no_turbo"
$ sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space"
$ sudo sh -c "echo performance > /sys/devices/system/cpu/cpu1/cpufreq/scaling_governor"
透過大數算法來解決 overflow 問題,並採取 iterative 方式,結果如下圖。 Fib(0) 和 Fib(1) 會直接回傳結果,所以執行時間較短,其他測試隨著 n 的增加運算所需時間都有顯著的提升,符合 的線性趨勢,不過雖然我們已經排除干擾效能分析的因素,但結果仍舊有顯著的抖動。
TODO:
fast doubling 需要實作大數運算的減法與乘法,乘法實作上較複雜,透過二層迴圈來走訪每個位數,將各位數做相乘,再將相乘結果向左位移到所屬的位數,依序計算並相加所有結果。
如下圖,fast doubling 的運算明顯比 Iterative 法更加耗時許多,並沒有如所預期的可以達到 的時間複雜度。
嘗試優化程式碼,可以觀察在做乘法時,雙重二層迴圈內顯然有很多次都是不必要的執行,如果執行乘法時其中一方為 0
,就不必執行乘法運算。
注意用語:「雙重」和「二層」意義不同,前者是隱含有對稱的兩件人事物,後者則著重在階層 (hierarchy),本例中,應採後者。
用來存放運算結果的 array 減小到只包含3個 element,這樣的大小就足夠存放。
再次測量結果可以發現執行時間大幅減小。
使用一致的程式碼縮排方式: 4 個空白
分析兩組 pseudo code,執行 Fast Doubling 的次數雖然相同,但方法二在執行大數加法運算的次數則減少許多。
以 Fib(100) 為例,
方法一執行 6 次 fast doubling 的運算 (變數 i 從 1 到 64 以 2 的倍數增加共執行 6 次),接著執行 35 次的大數加法(變數 i 從 64 到 99)。
方法二同樣執行 6 次 fast doubling 的運算,但僅執行 3 次的大數加法 ((n >> j) & 1
為 true 時會執行)。
clz
方法 2 中其實我們就已經利用 for 迴圈來計算從 MSB 算起有多少開頭 0 位元,但透過 gcc 提供的 built-in function clz
試圖加速。
測量結果如下圖,不過發現測試多次後,抖動相當明顯,
根據老師的建議,方向為
在上方排除效能干擾的因素段落中確實執行,且結果均有成功寫入,透過 Ubuntu 內建的 System Monitor 觀察,CPU1 確實被保留住。
命令 chrt 可以設定 process 的 real-time scheduling attributes,有以下 attributes 可以設定
SCHED_OTHER
: 這是一般 Linux 預設的排程方法,並不會對 process 做任何特殊處理。SCHED_FIFO
:使用 First-in-first-out scheduling algorithm。SCHED_RR
: 使用 round-robin scheduling algorithm。SCHED_BATCH
:這是 Linux 獨有的(since linux 2.6.23),只有在沒有任何 process 想要使用 CPU 時,他才能夠執行,被設定成 SCHED_BATCH 的 process 比 SCHED_OTHER 還低。SCHED_IDLE
: 通常使用在低優先級且在背景執行的 process。在此案例中,我們可將 scheduling policy 設定為 FIFO,任務的即時排程優先權改為 90
(不能設定為 99
,否則系統很可能無法正確運作),並且加大測試次數,這樣就可確保測試時間,被打斷的機會大幅降低,從而排除干擾。當然,還是需要將 outlier 去除,利用統計方法。
這裡我們使用 SCHED_FIFO
,根據老師建議給予優先權 90
。
這邊我有一個疑惑,其實從量測效能圖中,可以看到從 kernel space 所測得的時間就已經有明顯抖動,代表是否 kernel module 執行過程中發生被打斷的現象,但是我們使用 taskset 指令都是在確保 user space 的 process 不要被搶佔,這樣的動作是否對排除量測干擾並沒有幫助?是否能夠對 kernel module 設定較高的優先權來解決? 或是對 kernel module 設定 Processor affinity 來排除干擾?Yu-Hao(Howard) Hsu
因為你使用 ktime 測量時間,在 Linux 核心尚有其他方法。設計實驗需要逐步排除問題,不用急著一次套用一堆方法。
注意共筆書寫,中英文間用一個半形空白字元區隔
若是我們測量出來的數據近似於常態分佈的機率分佈,將會有 95% 的數值分佈在距離平均值 2 個標準差以內的範圍。透過執行 50 次的 user space program,將結果以不同標準差來去除離群值。
在 Makefile 裡重複執行測試程式50次並將結果存成不同的檔案。預期希望透過do while loop 來控制測試執行次數。
寫 Makefile 過程中,學習到以下 3 點,紀錄下來供未來參考:
When it is time to execute recipes to update a target, they are executed by invoking a new sub-shell for each line of the recipe, … Please note: this implies that setting shell variables and invoking shell commands such as cd that set a context local to each process will not affect the following lines in the recipe.
因此我們必須將整個 do while loop 寫成單行指令,以便達到預期成效,我們以分號(;)與換行符行 (\)來將所有指令串起。在 GNU Make 5.3.1 文件中也有提到你可以透過 .ONESHELL 來達成一樣的效果。
The program used as the shell is taken from the variable SHELL. If this variable is not set in your makefile, the program /bin/sh is used as the shell.
若是你沒有特別設定 variable SHELL
,預設都是以 /bin/sh
來執行。而不會是 bash ,不能使用 bash 語法混雜其中。
Variable and function references in recipes have identical syntax and semantics to references elsewhere in the makefile. They also have the same quoting rules: if you want a dollar sign to appear in your recipe, you must double it (‘
$$
’). For shells like the default shell, that use dollar signs to introduce variables, it’s important to keep clear in your mind whether the variable you want to reference is a make variable (use a single dollar sign) or a shell variable (use two dollar signs).
在 Recipe 中,如果使用的是 make variable,必須使用單一個 $
,若是使用 shell variable,須使用兩個 $$
。以下例子就是正確的使用變數的方式。
以下是在 makefile 中撰寫名為 profile 的 target,會執行程式 client 共 50 次並將依序存為檔案。
針對 kernel space 的執行時間進行觀察,以 python script 來分別以 68-95-99.7 法則來去除離群值,並將標準差範圍內的資料取平均,畫出以下圖表。
原始全部資料與以 68 法則去除離群值後的資料來對比
原始全部資料與以 95 法則去除離群值後的資料來對比
原始全部資料與以 99.7 法則去除離群值後的資料來對比
我的解讀是是 99.7 法則來去除離群值能夠將大部份 Fib(n) 的測量結果降低,會是比較符合我們預期的模型。但這部份該如何正確分析,還請老師給予指導。
至此,你不難發現,面對 GNU/Linux 和現代的電腦硬體,即便程式碼都是你自己撰寫,其行為卻無法充分掌握,你總是只能用較為迂迴的方式來分析,這也是我們為何要學習 perf, gdb, valgrind 等工具。在上述統計的實驗中,僅能適度剔除 outlier,不過資料本身的特性更為關鍵。
我們還需要排除更多干擾因素,諸如:
Cold cache: Cache is empty or has irrelvant data, so it needs to read data from main memory for your program.
Warm cache: Cache has relvant data that your program needs so it only access the cache without access main memory.
可以參考 ldotrg 同學的共筆來設定