contributed by < salmoniscute
>
參考程式碼
Multi-Precision Integer, MPI 實作了處理大數的功能,超越了標準整數類型的大小限制。
為什麼是
mpi_t[1]
而不是mpi_t
就好?
將結構體定義為固定長度為 1 的陣列
當宣告變數 mpi_t a;
時,a 本身就是一個含有一個結構體元素的陣列
因為在 C 中,陣列作為參數時會退化為指標
當傳遞 mpi_t 到函式時,實際上傳遞的會是指向結構體的指標
也就是說 mpi_init
函式會拿到指向 a 的第一個元素的指標
所以後續的程式就可以用像是 rop->capacity = 0;
和 rop->data = NULL;
的指標操作寫法,並且不需要使用 &
來取址。
大數被拆分為多個 31 位的區塊(每個區塊範圍是 0 到 ,即 0 到 )。
每個 data 元素存儲 31 位(使用 INTMAX = 0x7fffffff
來限制),而非完整的 32 位
如果在 data[0] 為 a, data[1] 為 b 的情況下,大數就會是 。以此類推。
static size_t ceil_div(size_t n, size_t d)
向上取整 (n + d - 1) / d
舉例來說,ceil_div(32,31)
= 2,代表一個 32 位的整數需要 2 個 31 位的區塊才可以完整表示。
int mpi_set_str(mpi_t rop, const char *str, int base)
將字串形式的十進制數字轉換成整數並儲存到變數 rop 中。
迴圈處理:
先將當前結果乘以 10,然後將當前字元的轉成數字(str[i] - '0')
並加到結果中。
以 mpi_set_str(n, "549755813889", 10);
為例:
可以知道 549755813889
的二進位表示為
1000000000000000000000000000000000000001
,一共有 40 個 digits,超過了 31 digits,所以需要 2 個 block 來存()。
data[0] 為 ,十進位為
data[1] 為 ,十進位為
不過現有程式碼因為在 mpi_mul_u32
函式裡面會一直增加 capacity 的值,因為乘數最大可能是 ,即 ,這樣會需要直接多一個 block 來表示。
因此目前測試結過會是以下輸出:
但是其實在 mpi_set_str
中,每次只會乘以 10。
在 mpi_gcd
函式是使用遞迴
改成用迭代的方法來做輾轉相除
SWAR 的基本概念是將多個「小型資料」打包進一個大型的暫存器中(例如 32-bit 或 64-bit register),然後透過整數運算一次處理這些資料。
就像考試說明中的例子
原本是要用 (x & 1) 和 (y & 1)
來檢查 x 與 y 是否為奇數。因為二進位中,奇數的最後一位一定是 1,所以 & 1 就能判斷。
使用 SWAR 的概念優化後
會直接把兩個 32-bit 的整數合併成一個 64-bit,然後一次性的跟 0x0000000100000001
做 &
。
把字元 d 擴展成一個 long 寬度的 mask,例如:
若 d = 0x2E,則 mask 會變成:
在 32-bit 裝置上:0x2E2E2E2E
這種填充的技巧讓後續的程式能夠同時比對多個 byte。
XOR 之後只在相同位元處為 0,因此若某個 byte 等於 d,XOR 後該 byte 為 0。接著利用 DETECT_NULL
巨集檢查是否有 0 byte。
不知道是哪個 byte,所以一一檢查確切位置。
4/16 上課剛好有討論到相關內容 所以先看第三題
Linux 核心搶佔
用 Linux 的 SIGRTMIN 加上 POSIX timer
註冊 preempt 函式作為 SIGRTMIN 的 handler。然後建立一個 POSIX timer,設定每 10ms 送出一次 SIGRTMIN。所以是每當時間到時,preempt 就會被呼叫。
preempt 是什麼?
就是呼叫 coro_yield
函式,強制當前 coroutine 暫停執行並讓出控制權給排程氣選擇的下一個 coroutine。
但是在 preempt 發生時,切到另一個 coroutine 前有件事要做:保存目前 coroutine 的 context
這樣等它之後要繼續跑時,才能從剛才中斷的地方繼續執行。這是透過 getcontext(&envs[curenv].state);
來做到的。
這邊的中斷不是指 interrupt,interrupt 是硬體層級的,這邊是透過 SIGRTMIN 這種 signal 來模擬。雖然有 timer,但他也不是真正的硬體 timer interrupt。一切都是跑在 user space。
state_reentered
是幹嘛的?
setcontext
回來),繼續工作。排程如何進行?
實作 round-robin 排程策略
coro_schedule
函式負責選下一個要執行的 coroutine。從 curenv 的下一個開始找,繞一圈(用 % NENV
),找到第一個 ENV_RUNNABLE 的就切過去
在切過去之前,它也會重設 timer,這樣 10ms 後會再進行一次 preempt。確保大家都有機會被執行到,不會被一個 coroutine 壟斷。
非常直觀