# 2020q1 Homework2 (fibdrv) contributed by < `fdgkhdkgh` > > [作業要求](https://hackmd.io/@sysprog/linux2020-fibdrv) ## 大數運算 在大數運算的使用上,是使用 [tiny-bignum-c](https://github.com/kokke/tiny-bignum-c) , 簡單來說,就是以一個陣列來儲存大數。並且可以依照自己的需求,看陣列要用多少個元素(並不是動態的)。用動態的應該會更好,可能可以提昇效能(I1)。 ## fast doubling 我是照著作業要求的 fast doubing 來寫程式碼,一開始先使用遞迴的方式,來計算出答案。可以嘗試使用 clz 或者不用遞迴來改效能(I2)。 ```cpp static void big_fib_sequence_v2(long long k) { if(unlikely(k <= 2)) { if(k == 0) { bignum_from_uint64(&(bigfibo[0]), 0); state[k] = 1; } else { bignum_from_uint64(&bigfibo[k], 1); state[k] = 1; } return; } else if(state[k] != 0) { return; } state[k] = 1; big_fib_sequence_v2(k / 2 + 1); big_fib_sequence_v2(k / 2); struct bignum tmp; struct bignum tmp2; if (k & 1) { bignum_mul(&bigfibo[k / 2], &bigfibo[k / 2], &tmp); bignum_mul(&bigfibo[k / 2 + 1], &bigfibo[k / 2 + 1], &tmp2); bignum_add(&tmp, &tmp2, &bigfibo[k]); } else { bignum_mul(&bigfibo[k / 2], &bigfibo[k / 2 + 1], &tmp); bignum_lshift(&tmp, &tmp, 1); bignum_mul(&bigfibo[k / 2], &bigfibo[k / 2], &tmp2); bignum_sub(&tmp, &tmp2, &bigfibo[k]); } } ``` :::danger 上述程式碼的 branch 數量過多,應予以縮減 :notes: jserv ::: ## 實驗成果 實驗的方式是在 kernel driver 裡實做出時間複雜度為 $O(N)$ 的 Fibonacci 數計算,以及 fast doubling。並用 ktime 系列的函式進行時間的計算,計算出運行的時間後,使用 sysfs 回傳給 user space 。原本的 client 沒辦法從 kernel space 拿取太長的輸入,所以改成使用 copy_to_user 將結果以字串的形式回傳到 user space。 ```cpp static ssize_t fib_read(struct file *file, char *buf, size_t size, loff_t *offset) { ktime_t start; ktime_t end; // set experiment environment for (int i = 0; i < 4000; i++) { state[i] = 0; } start = ktime_get(); big_fib_sequence_v3(*offset); end = ktime_get(); foo = ktime_to_ns(end) - ktime_to_ns(start); bignum_to_string(&(bigfibo[*offset]), message, sizeof(message)); copy_to_user(buf, message, strlen(message)); return (ssize_t) strlen(message); } ``` --- ![](https://i.imgur.com/wk6HsPU.png) kernel space, 使用 4 個 uint32_t 來表示大數 --- ![](https://i.imgur.com/iM0fNtX.png) kernel space, 使用 5 個 uint32_t 來表示大數 --- ![](https://i.imgur.com/847aKqc.png) kernel space, 使用 7 個 uint32_t 來表示大數 --- ![](https://i.imgur.com/ZVXAb9I.png) kernel space, 使用 11 個 uint32_t 來表示大數 --- 理想上,應該是 fast doubling 的效率會更高。所以我猜是我大數運算的效率太差 (I3) 。並且陣列長度越多,效能越低。所以可以嘗試使用動態陣列,讓所需的陣列長度越短越好 (I1) 。就猜測,大數運算本身運算的方式 (I3) ,以及表示的方式 (I1) 都會影響大數運算的效能。 ## 提昇效能 在這裡,我把我所猜測可以增加 fibdrv 效能的因素紀錄下來,並嘗試。每當完成一項,就再進行一次效能測試( 比較 fast doubing 與普通 $O(N)$ 間的效能差異)。 並且在實驗結果中,也發現幾個離群值,也可以嘗試看看有什麼減少~~雜訊~~離群值的技巧 (I4) 。 :::danger 避免濫用「雜訊」這詞彙,複習統計學用語 :notes: jserv ::: ### Improve 1 (I1) 改成動態陣列表示大數運算 我目前所使用的大數運算的問題是,沒有紀錄目前的數字會使用陣列中的幾個元素,於是在做許多操作的時候都會需要遍尋所有的元素。於是動態陣列(可知道使用了多少個元素),就我的猜測可以加速大數運算的許多操作。[老師的大數運算實作](https://github.com/sysprog21/bignum/blob/master/bn.h)很值得參考。 ### Improve 2 (I2) 使用 clz 以及不使用遞迴 我原本是簡單的照著遞迴式子,從數字最大的地方往下遞迴求解 (top down) 。而[老師的例子](https://hackmd.io/@sysprog/linux2020-fibdrv#-%E8%B2%BB%E6%B0%8F%E6%95%B8%E5%88%97)則是從底處開始求值 (bottom up) , bottom up 的好處是不需要遞迴求解了。可以省下遞迴時的 stack frame 以及執行遞迴的程式時所花費的時間。 首先試著將其改成 bottom up: ![](https://i.imgur.com/2BrMciE.png) :::danger 避免將最初的 Fibonacci 實作稱為 "normal",不然其他都隱含 "abnormal" 的意思了。可改稱 "naive" (有幼稚、原始和初步等含義,不是 "native") :notes: jserv ::: 變慢的原因有待查證。 ### Improve 3 (I3) 改善大數運算 我原本用的大數運算,在乘法的時候最高複雜度會到 $O(N^3)$,這應該就是效能瓶頸了。簡單的實作可以到 $O(N^2)$,而老師使用的 Karatsuba algorithm 在 k 很大時,會比原本的 $O(N^2)$ 的實作還要快上一些。 $Karatsuba~~algorithm:$ $U=U_1*2^N+U_0$ $V=V_1*2^N+V_0$ 且 $Z_2=U_1*V_1$ $Z_1=U_1*V_0+U_0*V_1$ $=\color{red}{(U_1+U_0)*(V_1+V_0)-Z_2-Z_0}$ $Z_0=U_0*V_0$ --- $U*V=(U_1*2^N+U_0)*(V_1*2^N+V_0)$ $=(U_1*2^N)*(V_1*2^N)+U_0*(V_1*2^N)+V_0*(U_1*2^N)+U_0*V_0$ (4 次乘法) $=U_1*V_1*2^{2N}+(U_0*V_1+U_1*V_0)*2^N+U_0*V_0$ $=Z_2*2^{2N}+Z_1*2^N+Z_0$ $=Z_2*2^{2N}+((U_1+U_0)*(V_1+V_0)-Z_2-Z_0)*2^N+Z_0$ $=Z_2*(2^{2N}-2^N)+((U_1+U_0)*(V_1+V_0))*2^N+Z_0*(1-2^N)$ $=\color{red}{(U_1*V_1)}*\color{green}{(2^{2N}-2^N)}+(\color{red}{(U_1+U_0)*(V_1+V_0))}*\color{green}{2^N}+\color{red}{(U_0*V_0)}*(1-2^N)$ (三次$\color{red}{乘法}$,其餘為$\color{green}{位移}$與$加減)$ --- 要注意的是, $(U_1+U_0)*(V_1+V_0)$ 式子裡的加法可能導致位數變多而拖到效能,於是可用另外一種形式: $Z_1=U_1*V_0+U_0*V_1$ $=\color{red}{(U_1+U_0)*(V_1+V_0)-Z_2-Z_0}$ $=\color{red}{(U_1-U_0)*(V_1-V_0)+Z_2+Z_0}$ 證明: $(U_0-U_1)*(V_1-V_0)+Z_2+Z_0$ $=U_0*V_1\color{red}{-U_0*V_0}\color{green}{-U_1*V_1}+U_1*V_0\color{green}{+U_1*V_1}\color{red}{+U_0*V_0}$ $=U_0*V_1+U_1*V_0$ $=Z_1$ --- $U*V=(U_1*2^N+U_0)*(V_1*2^N+V_0)$ $=(U_1*2^N)*(V_1*2^N)+U_0*(V_1*2^N)+V_0*(U_1*2^N)+U_0*V_0$ (4 次乘法) $=U_1*V_1*2^{2N}+(U_0*V_1+U_1*V_0)*2^N+U_0*V_0$ $=Z_2*2^{2N}+Z_1*2^N+Z_0$ $=Z_2*2^{2N}+((U_1-U_0)*(V_1-V_0)+Z_2+Z_0)*2^N+Z_0$ $=Z_2*(2^{2N}+2^N)+((U_1-U_0)*(V_1-V_0))*2^N+Z_0*(1+2^N)$ $=\color{red}{(U_1*V_1)}*\color{green}{(2^{2N}+2^N)}+(\color{red}{(U_1-U_0)*(V_1-V_0))}*\color{green}{2^N}+\color{red}{(U_0*V_0)}*(1+2^N)$ (三次$\color{red}{乘法}$,其餘為$\color{green}{位移}$與$加減)$ 並且不會在計算 $(U_0-U_1)*(V_1-V_0)$ 時多一位數 => 提升效能 :::danger 用 LaTeX 語法改寫上述 :notes: jserv ::: 但目前還在理解老師的實作,於是我先試著改善原本 [tiny-bignum-c](https://github.com/kokke/tiny-bignum-c) 的實作,更改其乘法的實作方式。 ```cpp void bignum_mul(struct bn *a, struct bn *b, struct bn *c) { struct bn tmp; int i, j; bignum_init(c); DTYPE upper; DTYPE lower; for (i = 0; i < BN_ARRAY_SIZE; ++i) { for (j = 0; j < BN_ARRAY_SIZE; ++j) { if (i + j < BN_ARRAY_SIZE) { DTYPE_TMP intermediate = ((DTYPE_TMP) a->array[i] * (DTYPE_TMP) b->array[j]); lower = intermediate & MAX_VAL; upper = intermediate >> (8 * WORD_SIZE); intermediate = (DTYPE_TMP) lower + (DTYPE_TMP) c->array[i+j]; c->array[i+j] = intermediate & MAX_VAL; intermediate = intermediate >> (8 * WORD_SIZE); if (i + j + 1 < BN_ARRAY_SIZE) { intermediate = (DTYPE_TMP) upper + (DTYPE_TMP) c->array[i+j+1] + intermediate; c->array[i+j+1] = intermediate & MAX_VAL; intermediate = intermediate >> (8 * WORD_SIZE); int k = i + j + 2; while (intermediate > 0 && k < BN_ARRAY_SIZE) { intermediate = (DTYPE_TMP) c->array[k] + intermediate; c->array[k] = intermediate & MAX_VAL; intermediate = intermediate >> (8 * WORD_SIZE); } } } } } } ``` --- ![](https://i.imgur.com/modG41h.png) user space, 使用 11 個 uint32_t 來表示大數 --- 效率比之前快上許多, fast doubling 終於快要趕上 normal fibonacci 了! 將這個方法實作在 kernel driver 上: ![](https://i.imgur.com/ni0qlnv.png) kernel space, 使用 11 個 uint32_t 來表示大數 --- 但比起老師的實作,還是慢上許多 (慢了 20 倍左右),之後希望可以開始進一步研究老師的實作。 ### Improve 4 (I4) 減少測試效能時,會遇到的雜訊 依照作業需求提供的提示,將行程固定在某個 CPU 上運行、關閉 ASLR 、設定 scaling_governor 為 performance 、 針對 Intel 處理器,關閉 turbo mode 。並再測試一次效能,可以看到紅線的震盪有變得比較小,但還是會有抖動的情況。 --- ![](https://i.imgur.com/vHaSKZs.png) kernel space, 使用 11 個 uint32_t 來表示大數 --- ### Improve 5 移植老師的 [bignum](https://github.com/sysprog21/bignum) 到 linux kernel driver ![](https://i.imgur.com/ub38GZ5.png) teacher's fast doubling : 老師實作的 fast doubling my original fast doubling : 我最一開始實作的 fast doubling , 在每一輪會使用 4 次的乘法 my fast doubling : 參考老師實作後,精簡成使用 3 次乘法 naive fibonacci : 使用單純的加法來計算 fibonacci 將數字放大後,可以明顯地看出 fast doubling 的威力,明顯地比 naive fibonacci 快上許多。 ### Improve 6 減少雜訊 我使用的作業系統是 ubuntu 18.04 1. 修改 /etc/default/grub ```=shell GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=0" ``` 2. sudo update-grup 3. sudo reboot 這時候假如設定有成功的話, ```taskset -cp 1``` 不會顯示 cpu0 為可用 ex : ``` pid 1 目前的近似者清單:1-5 ``` 若沒有成功的話,就會顯示所有 cpu 都可用 ex : ``` pid 1 目前的近似者清單:0-5 ``` 4. 抑制 address space layout randomization (ASLR) 5. 設定 scaling_governor 為 performance。 6. 針對 Intel 處理器,關閉 turbo mode 7. ```sudo taskset 0x1 executable``` (taskset 的 cpu 從 1 開始) ![](https://i.imgur.com/dRjC6Ma.png) 由圖可看出離群值少了許多。以及每一輪使用 4 次乘法的 ```my original fast doubling``` 比另外兩種 fast doubling (每一輪使用三次乘法)慢上一些。 ## TODO - [x] 撰寫簡單的 linux kernel driver - [x] 了解 firbdrv 的架構(例如 user space 是怎麼跟 kernel space 進行溝通的) - [x] 撰寫完整的功能 (fibdrv, python test script) - [x] 如何對核心模組測試效能(例如核心模式的時間測量) - [x] 加速 費氏數列的運算 (fast doubling....) - [x] 精進核心模組的測試方式(減少雜訊) - [ ] 閱讀 linux device driver , 弄清楚所有相關名詞與 function - [ ] 自己實作屬於自己的大數運算 - [ ] 了解[老師的大數運算實作](https://github.com/sysprog21/bignum),並改進自己的實作方式 - [ ] 使用 clz 加速 fast doubling 的運算 ## Reference [1][Writing a Linux Kernel Module — Part 1: Introduction](http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/) 寫一個 hello world 的 device driver 。 [2][Writing a Linux Kernel Module — Part 2: A Character Device](http://derekmolloy.ie/writing-a-linux-kernel-module-part-2-a-character-device/) 寫一個 character device ,會從第一個範例延伸至另一個 加了 mutex 的範例。也會教導如何使用 udev rule 來調整存取裝置節點的權限。(雖然範例在我的環境執行時會崩潰) [3][Introduction to Linux kernel driver programmingdriver programming](https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Introduction-to-Linux-Kernel-Driver-Programming-Michael-Opdenacker-Bootlin-.pdf) 介紹 linux kernel driver , 以 USB 為例,簡略地走過 linux kernel driver 的程式碼架構 [4][陳鍾誠教授:VFS介紹](http://sp1.wikidot.com/linuxvfs) [5][Big-Number sysprog21-bignum](https://github.com/sysprog21/bignum) [6][Big-Number Arbitrary-precision_arithmetic](https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic) [7][Big-Number Small portable multiple-precision unsigned integer arithmetic in C](https://github.com/kokke/tiny-bignum-c) [8][Sample kobject implementation](https://elixir.bootlin.com/linux/v5.3/source/samples/kobject/kobject-example.c) [9][快速的乘法](https://pansci.asia/archives/162365) [10][關閉與開啟 ASLR](https://blog.csdn.net/counsellor/article/details/81543197) ###### tags: `linux2020`