contributed by < Jerejere0808 >
新增以下資料結構使 fibonnaci 可以進行大數運算,其中 capacity 是用於分配多餘的記憶體空間,這樣就可以避免多次的 resize (realloc) , 只有在 capacity 不足時才會需要重新分配記憶體空間。
將計算 fibonnaci 的方法改成 fast doubling,使時間複雜度可以達到 log(n)。
注意就算使用大數運算仍會有運算數字大小的限制。因為 kmalloc 會有分配空間的限制,分配超過某個 size 會導致錯誤的情況,而分配空間的限制跟每台電腦的系統配置有關,如果假設最大的分配空間為 4MB , 那麼根據 Binet 公式 , 當 n 為足夠大的正整數時可以推算需要使用的位元數: − 1.160964 + n × 0.694242
當 n = 5761680 , f(n) 就會需要 4MB 來表示,所以當 n > 5761680 就會產生錯誤。如果是以 struct _bn 裡的 unsigned int 陣列來看,Bytes 數量就必須除以 32 加 + 1 ,也就是 unsigned int *number 最多可以分配 125000 個 unsigned int。
可以用以下方法來測量在 kernel 所需多少計算時間
參考 colinyoyo26 的方法用 clock_gettime 在 client 取得 user 所需的時間
兩者相減可以得到 kernel to user 的時間,如下圖:
不過我發現所需的時間比預期 log(n) 還多出許多,fib(300) 就需要 50000 ns 經過檢查,發現我把 bn_to_string ,也就是轉化成字串的部分算進去 kernel time 裡面了 ,把這部分的計算時間去掉之後,時間有明顯變短,如下圖所示,可見把 bn 轉成字串是非常耗時間的。
再持續修改,原本的 bn_fib_fdoubling 有 bn_cpy 也就是需要複製資料,這樣會需要額外成本,因此透過修改 bn_lshift 使 src 可以直接把左移的結果給 dest 就不用先複製之後又再往左位移。
ex: bn_cpy(k1, f2); bn_lshift(k1, 1); 可以改成 bn_lshift2(f2, 1, k1)
bn_cpy 和 舊的 bn_lshift 如下
新的 bn_lshift 和 fast doubling 實作如下:
沒有使用 bn_cpy 的執行速度如下圖:
在作業的說明中 bn_lshift 原本沒有
但是我發現這樣算出來的f(92)的結果會是錯誤的,然後我取到 f(1000) 發現後面某些值也是錯的,所以單獨檢查 f(92)。
上圖為當計算到92的最後一個bit時, bn_lshift2(f2, 1, k1) 計算結果是錯的,也就是 printk編號 0 到 1 的時候 k1 應該要變成 f2 的兩倍(2971215073 * 2 = 5,942,430,146) , 但卻變為 1647462850,導致後面一系列的錯誤,所以問題出在bn_lshift 。 shift > z 時 src 也要resize, 不然會導致當 shift > z 為真時,bn_resize(dest, src->size + 1) 之後 dest 比 src 多出 32 bit,也就是一個 number 然後 for (int i = src->size - 1; i > 0; i–) i 是從 src->size - 1 開始,也就是 dest 最後面那個 number 沒有被賦予值,最後結果錯誤。
下面是用 fdoubling 計算 fb(1) 到 fb(1000) 並且用 perf 去做效能量測的結果。
下面是用 fdoubling 且避免使用bn_ cpy 計算 fb(1) 到 fb(1000) 並且用 perf 去做效能量測的結果。
根據作業提示再進一步調整將原本的fast doubling 公式
利用Q-Matrix
還在研究為何換個公式就可以減少執行時間…
再用perf測量一次 時間有下降一些
下圖為有 bn_cpy 和 沒有 bn_cpy 的fast doubling 執行時間比較圖,可以看到後者的表現好非常多
下圖為沒有 bn_cpy 的 fast doubling 和利用Q-Matrix 且沒有 bn_cpy 的 fast doubling 執行時間比較圖,可以看到後者的時間相對較少。