contributed by < wurrrrrrrrrr
>
首先看到 ceil_div
是無條件進位除法:
他的作用為計算 n 除以 d,其中 n 可以表示為 q × d + r
,其中 r 是餘數,範圍為 0 到 d-1。若 r == 0
,表示整除,則結果為 q;若 r > 0
,則需向上取整,結果為 q + 1
,為了達成這個向上取整的效果,關鍵是觀察當 n 沒有整除時,餘數 r 的範圍會落在 1 到 d - 1。因此,而如果餘數為 1 時想進位 n 就要加 d-1 ,而如果餘數為 d - 1
時想進位 n 只要加 1,為了涵蓋所有可能的餘數,我們選擇固定補上 d - 1,也就是使用公式 (n + d - 1) / d
。d - 1
這個數值雖然在某些情況下可能超過進位所需要的,但它不會導致進位超過一位,而且又能保證所有餘數大於 0 的情況下都能正確進位。
AAAA
= (n+d-1)/d
第二個 mpi_set_u64()
這個函式的作用是把 uint64_t
拆解成多個 31
bit 並儲存在 mpi_t
結構中。一開始計算要幾個 31
bit來表示 64
bit 的數值,而第二段是確保 rop 有足夠空間來放這些 31
bit,如果記憶體不夠,會擴展它,再來執行了一段迴圈把 64
bit 的整數 op 拆成 31
bit 的小塊儲存到 rop->data[] 中,從低位到高位依序填入,而要如何拿出 31 bit?此時就需要一個 mask 只取後 31 位,因此 INTMAX = 0x7fffffff
。
若記憶體不夠,會分配或擴展。
BBBB
= 0x7fffffff
mpi_mul_naive 函式的作用是計算兩個 op1 × op2 的乘積,並將結果儲存到 rop 中。
其中的這段模仿我們手動筆算乘法的方式:
而其中 op1[n] × op2[m]
的結果會放在第 n + m
位開始,就像十進位乘法的位移,因此 CCCC
= n + m
mpi_fdiv_qr
的作用是執行整數的整除與餘數運算,也就是計算 q = n / d
和 r = n % d
,並將結果分別儲存在 q 和 r 中:
例子:當 n 為 1101 時
一開始:
n 是 1101 ,最高位是第 3 位
每個步驟:
第 2 步:i = 2
第3步:i=1
第4步:i = 0
最後結果:
mpi_mul_2exp(r, r, DDDD)這行的作用是將餘數 r 左移 1 位,因此DDDD
= 1
。
mpi_gcd 這個函式實作的是輾轉相除法來計算兩個整數 op1 和 op2 的最大公因數。依照輾轉相除法的原理:
若 b ≠ 0,則 GCD(a, b) = GCD(b, a mod b)
所以,在遞迴的情況下,只要 op2 不為 0,就持續以 op1 % op2 的餘數作為新的下一輪的參數,直到餘數等於 0 為止。
EEEE
= mpi_gcd(rop, op2, r)
下列這段程式為 mpi_gcd 的非遞迴實作版本:
延伸問題:
解釋上述程式碼運作原理
這項實作依賴遞迴呼叫,請避免使用並降低記憶體開銷
改進最大公因數的運算效率
在測驗 2 中可以發現,這個函式中的((y + 0L) & (-1L >> 32))
是多餘的。
原因是,我傳入的 y 是uint32_t
類型(無號整數),
在進行 (y + 0L) 時,C 語言會將 y 進行無號升型(promotion),
轉換成 long 型別,並且高位自動補 0,不會產生符號擴展或高位污染的問題。
為了確認這段我做了測試:
而印出來的結果為
後面的 & (-1L >> 32) 遮罩操作實際上並沒有任何作用,可以直接省略。
另外,如果真的需要截取後32位元,為了保證正確性與跨平台行為,
應該明確使用 unsigned long 型別進行操作,例如遮罩 0xFFFFFFFFUL,
而不是使用 (-1L >> 32) 這種有號數右移行為的寫法。
為了驗證上述這段我做了以下測試:
此為輸出結果:
而 memchr_opt 分為三個階段,其中要填入的為第二段
第一段:逐字元搜尋 c 直到記憶體位置對齊
第二段:快速以 long 單位搜尋是否包含目標字元 c
第三段:逐字元搜尋剩餘字元是否包含目標字元 c
首先看到這段:
從題目的敘述可以知道 DETECT_NULL(X) 會檢查 X 中是否有 byte 是 0x00。
我們想判斷某個 long 變數是否包含特定字元 c,做法是:
將該 long 變數 X 與一個由目標字元重複填滿的 mask 做 XOR,若 X 中有任何 byte 等於目標字元,則 XOR 結果對應 byte 就會是 0,等同 DETECT_NULL 可檢測到,因此 AAAA = X^mask
由上述可得知,mask 是由目標字元 d 重複填滿,而下列這段是想產生一個 unsigned long mask,裡面所有的 byte 都填滿字元 d。
初始時先用 16 bits 來填滿(d << 8 | d),接著利用位移與 OR 操作將其擴展到 32 bits。
如果 unsigned long 是 64 位元,會執行下面的迴圈,持續將目前的 mask 左移並 OR 到自己,直到整個 64-bit mask 都被填滿:
因此可以得知 BBBB = mask<<i
而 CCCC 則是指向資料區塊的 unsigned long 變數,因此 CCCC = *asrc
,用來進行目標字元的檢測
延伸問題:
解釋上述程式碼運作原理
比較 Linux 核心原本 (與平台無關) 的實作和 memchr_opt,設計實驗來觀察隨著字串長度和特定 pattern 變化的效能影響
研讀 2022 年學生報告,在 Linux 核心原始程式碼找出 x86_64 或 arm64 對應的最佳化實作,跟上述程式碼比較,並嘗試舉出其中的策略和分析
下一步:貢獻程式碼到 Linux 核心
Phoronix 報導
AAAA = ENV_RUNNABLE
BBBB = attempts+1
CCCC = coro_schedule
DDDD = coro_yield
用 0xC 當例子
初始: crc = 0x0000000C //(binary 1100)
0x0000000C 這樣的初始值,其高位元可以假設為 0,原因是後續的運算中,每次都會進行 crc >> 4 的位移操作,把高位資料不斷移除掉,因此原本高位的內容不會影響最終的結果。
第一次位移 (bit 0)
第二次位移 (bit 1)
第三次位移 (bit 2)
第四次位移 (bit 3)
crc & 1 == 1
再次先右移再 XOR 0x82F63B78:
crc = 0xC38D26C4
以此類推
AAAA
:0xc38d26c4
BBBB
:0xd3d3e1ab
CCCC
:0xe330a81a
DDDD
:0xf36e6f75
延伸問題:
在 Linux 核心原始程式碼中找到 CRC32 的應用案例至少 3 處,並探討其原始程式碼
設計實驗來分析 Fast CRC32 提出的手法 (要涵蓋 branchless 和查表的變形),並與硬體的 CRC32 指令效能進行比較。
以 x86-64 或 Arm64 為探討標的,說明 Linux 核心如何使用硬體加速的 CRC32 指令
AAAA
:0x7FFF
BBBB
:0x80000000
CCCC
:0x7FFFFFFF
DDDD
:0x80000000
EEEE
:0x7FFFFFFF
FFFF
:input & 0xFF
GGGG
:Q15_MAX
HHHH
:Q15_MIN
延伸問題:
解釋上述程式碼運作原理,搭配「信號與系統」教材
探討定點數的使用和其精確度
改進 MIDI 一閃一閃亮晶晶的音效輸出
IIII
: max_fd
JJJJ
: &rfds
這支 server 程式是一個多人聊天室伺服器,支援同時多個 client 連線進來聊天,每當有一個 client 發訊息給 server,server 會自動把這個訊息轉發給其他所有連線中的 client,而且如果有 client 離開,server 也會自動偵測並清除連線。
執行這支程式,server 在本機的你輸入的 port 上開始監聽,等待 client 連進來。
client 可以用像是 telnet 或 netcat 來連線到 server ,例如:
每成功一個連線,server 端會:
client 發送可以訊息給 server
client 任意打字,例如: Hello everyone!
server 端在下一輪 select() 中偵測到這個 client fd 有資料可以讀。
server 處理收到的訊息
當 server 偵測到某個 client 有資料:
呼叫 read(fd, buf, sizeof(buf)) 讀取資料。
如果 read() 回傳 > 0,代表真的有資料收到。
接著 server 會:
client 斷線時的處理
如果某個 client:
自己正常關閉連線或是網路問題中斷 server 端在 read(fd, buf, sizeof(buf)) 時會遇到: read() 回傳 0
這時 server:
延伸問題:
解釋上述程式碼運作原理
學習 jserv/ttt 程式碼,在不使用 (n)curses 一類現成的函式庫前提下,改進網路聊天室的文字呈現介面
在既有的程式碼之上,實作帳號登入 (login) 及基本管理功能,並該有線上與否的提示,留意 ping value 的處理機制