or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Do you want to remove this version name and description?
Syncing
xxxxxxxxxx
2023q1 Homework3 (fibdrv)
contributed by < LiChiiiii >
修正 \(Fibonacci()\) 的缺陷
加速計算 \(Fibonacci()\)
原本的程式碼是利用 dynamic programming 來計算,時間複雜度為 \(O(n)\),空間複雜度為 \(O(n)\) 。因此改為使用 clz / ctz 一類的指令,搭配 Fast Doubling 使得時間複雜度降到 \(O(logn)\),空間複雜度降到 \(O(1)\) 。
原本的費氏數列定義為:
\[ \begin{split} F(k-1) &= F(k+1) - F(k) \end{split} \]
經過矩陣的推導後,可得到對於奇數和偶數的不同算法:
\[ \begin{split} F(2k) &= F(k)[2F(k+1) - F(k)] \\ F(2k+1) &= F(k+1)^2+F(k)^2 \end{split} \]
根據這個公式,每次進入
recursive
的數字一定是前一個數字的一半,而且recursive tree
不會有增長,每次呼叫只會重複呼叫自己一次而已。程式碼運作原理
使用
__builtin_clzll
來計算有多少 leading 0s。遇到
0
: 求 \(F(2n)\) 和 \(F(2n+1)\)遇到
1
: 求 \(F(2n)\) 和 \(F(2n+1)\) ,再求 \(F(2n+2)\) (第 15 行的條件式)以 \(F(6)\) 為例: 610 = 1102
計算 \(F(93)\) (包含) 之後的 Fibonacci 數
自定義結構以計算大數
原始程式碼以
long long
來儲存費氏數列的計算結果,但是在 \(F(93)\) 之後的運算會發生 overflow,導致無法正確地計算結果。因此自行定義結構BigN
來儲存 128 位元的整數:其中
lower
表示低位的 64 位元,upper
表示高位的 64 位元。除了定義結構外,還定義其他函數來實作
BigN
結構體的運算,包含加法運算、減法運算、乘法運算、右移 1 bit、左移 1 bit。其中BigN的乘法運算使用左移右移和加法運算來實作,減少原乘法運算的成本。加法運算
各自計算兩個 64 位元整數的和,若
res.lower < a.lower
則要進位upper
。減法運算
各自計算兩個 64 位元整數的差,若
a.lower < b.lower
則要借位upper
。乘法運算
使用了一個
while
循環來將b
不斷右移,同時將a
左移。在每次循環中,如果b
的最低位是1,則將a
加入到 res 中,最終得到a
和b
的乘積res
。右移 1 bit
左移 1 bit
最後利用上述自定義的結構和函數來更改程式碼:
由 client 端印出結果
原始程式碼在 fibdrv.c 中的
fib_read()
回傳計算出的 Fibonacci 數,最初想法是將fib_read()
回傳的ssize_t
的型態更改成自定義的BigN
傳給 client ,但是不能隨意更改 read ,且在使用者模式的位址空間配置一個 buffer 空間時,核心裝置驅動不能直接寫入該地址,因此使用到 copy_to_user ,將想傳給使用者模式 (即運作中的 client) 的值複製到到fib_read()
的 buf 參數後,client 端方可接收到此值並印出。fibdrv.c
client.c
開發過程紀錄
上述程式碼修正過後發現 \(F(93)\) 可以得到正確的數值,但是 \(F(94)\) 之後依然是錯誤的。那是因為在
BigN_add()
中的res.lower = a.lower + b.lower
發生了 overflow 。我嘗試加上
(a.lower+b.lower) < a.lower
判斷低 64 位元在相加時是否發生 overflow ,若發生則宣告 128 位元的temp
存放相加後的值,再將temp & 0xFFFFFFFFFFFFFFFF
取出低 64 位元,並右移 64 位元取得進位的值(第 8 、 9 行)。修正完的結果依然是錯誤的。
舉例來說, \(F(94)\) = 19740274219868223167 ,在
BigN_add()
希望可以得到res.upper = 1
和res.lower = 9740274219868223167
,但19740274219868223167
在二進制取出的低 64 位元得到的會是1293530146158671551
。因此我嘗試將第 8 行改成使用 mod 來得到我想要的餘數,卻出現以下錯誤訊息:
顯然必須避免使用 128 位無符號整數取模運算。
我嘗試將 upper 轉成 __int128 的型態並左移 64 bits(就是乘上 \(2^{64}\) 的意思),接著將 upper 和 lower 轉成字串之後相加,來避免 overflow 的問題。最後把欲回傳運算結果轉成字串後存入 buf ,再由 client 印出存在 buf 的字串。
定義函數實作 128 位元無號整數轉成十進位字串以及將兩個十進位字串相加
字串反轉
128 位元的整數轉成字串
兩個字串相加
更改結果成功印出正確數值。
時間測量和效能分析
使用
ktime
在核心模組中測量執行時間,發現fib_write
此暫時沒作用,因此在此函式實作並回傳 kernel space 的執行時間。使用
clock_gettime
計算在呼叫fib_write
時所需的時間,可得到 user space 的執行時間。兩數相減即可得到 system call 的時間。用統計手法去除極端值
導入 driver.py 去除 95% 區間之外的值,可以把圖中的尖端消除掉。
