contributed by < rota1001
>
fennecJ
這個專題是要引進 Timsort 以改良 linux 中的 list_sort
,並以實驗說明其好處。
此演算法的前提是現實世界中的大部分資料都是部份排序的,所以將部份排序良好的陣列合併成一塊一塊(runs),最後將他們合併。
原始的版本是在陣列上,會決定一個 minrun
,代表一個 run 最小的長度。從陣列的開頭開始蒐集元素組成 run,選取連續遞增或嚴格遞減的元素連起來,如果他的長度小於 minrun
就用插入排序法將後面的元素插到 run 裡面。minrun
通常選 64,因為它可以放進 cacheline 中,讓插入排序法效益較高。然後它可以二分搜應該插入的位置,這樣雖然插入還是需要
這裡如果是只有 3 個的話在符號上就會是
如果不滿足任何一條的話,那
如果插入一個處理到滿足性質後,就會接著插入另一個 run,直到所有 run 都插入完成。這個時候整條序列會滿足至少以費式的增長速度遞增,再從堆疊頂部開始一路合併到堆疊底部。
這裡使用Timsort 研究與對 Linux 核心貢獻嘗試的例子來解釋:
這是 Timsort 的一種改良,將堆疊的性質檢查做修改,對於每次插入 run 時,頂部的 3 個元素
否則將
這也是對於 Timsort 合併時的改良。前述的方式可以歸納為保證從堆疊頂部開始的某種指數的增長,然而這會造成一些情況上不平衡的合併(差距很大的 run 互相合併)。Powersort 的概念是去貼近 top-down merge sort 對於陣列的切割。
在 COMP526 (Fall 2023) 3-6 §3.6 Python's sorting method & Powersort 的 12:47 可以看到一個圖,這個圖是代表在進行 top-down merge sort 時會去進行的切割,編號為 1 的將陣列切成 2 塊,編號為 2 的將陣列切成 4 塊,編號為 3 的將陣列切成 8 塊。接下來,對於兩個 runs 來說,他們的中點所為成的區間蓋住的編號中最大的數字就是這兩個 runs 中間的那個縫隙的 power。對於每個 run 來說,他的 power 就是它左邊的縫隙的 power,如果是第 0 個 runs 的話,那他的 power 就是 0。合併方式是這樣的,每次新增 run,對於堆疊頂部的
這裡想到一種用機率的觀點來直觀的思考 powersort。首先,一個固定長度的區間,蓋住一個編號較大的切割是比蓋住一個編號較小的切割機率更大的,而且由分佈密度來看的話機率相對於編號的遞增是指數遞減的,另外,區間長度越長所能蓋住某個特定的切割的機率更大,而且這個機率是線性遞增的。換句話說,如果某個區間蓋住了某個切割,那相對於這個切割的編號,這個區間的長度的期望是指數遞增的。所以這個縫隙數字為
而以實驗結果看來,Timsort 與其變體確實能在特定的資料上有顯著的效率改善。
Commit 527edb2
static inline size_t nodePower(struct list_head *h1, struct list_head *h2,
size_t start_A, size_t len)
{
if(!h1 || !h2){
return 0;
}
size_t len_2 = len << 1;
size_t start_B = start_A + run_size(h1);
size_t end_B = start_B + run_size(h2);
size_t l = start_A + start_B;
size_t r = start_B + end_B + 1;
int a = (int)((l << 31) / len_2);
int b = (int)((r << 31) / len_2);
return __builtin_clz(a ^ b);
}
第 12 行和第 13 行中 l
和 r
的型別是 size_t
,他將它左移了 31 位,然而,我們不能期待 size_t
是 64 位元的,尤其是它可能運行在 32 位元架構上。所以,改成 uint64_t
會比較適應多平台。而 __builtin_clz
傳入的參數是 unsigned int
,這裡又使用位元運算,所以 12 和 13 行應該轉型為 unsigned int
更適合。另外,第 4 到 6 行,if
之下只有一行,應該將大括號省略。
queue.c
中引入 powersort我發現,powersort 適合運用在 q_merge
,於是在 Commit 7c2224a 將其引入。
teresasa0731
這篇專題大略是在各面向分析各種偽隨機數,並且觀察 Linux 的隨機數生成流程。
評價一個偽隨機數的考慮因數有以下:
這是一種線性平移的隨機數,在統計學上隨機,但是次數多起來後效率有明顯下降。
Linux 的隨機數是蒐集硬體產生的隨機資訊(物理干擾)進行一些處理放進熵池中,並且用熵來評價這個熵池的亂度。/dev/random
會等待 I/O 到熵池的熵足後取得亂數,而 /dev/urandom
會用安全的加密演算法來補足隨機性資料。
接下來在 Raspberry Pi 4 上裝 Linux,它是由硬體提供應用層亂數的,以此來觀察 /dev/random
在熵池不足的情形。
首先做了 FIPS 140-2
的測試,發現表現良好。
後面做了效率測試,發現到一定程度的數量後,出現熵池不足的問題,開始等待,如前面所訴。
前面在評估 xoroshiro128+ 的時候,沒有評估到安全性這一塊,這裡補充一下。
xoroshiro128+ 是一種 LFSR,他的操作並不複雜,在拿到夠多資料的情況是可以將種子計算出來。說起來簡單,這裡使用 z3-solver 去進行預測:
import os
from Crypto.Util.number import bytes_to_long
import z3
def rotl(x, k):
if type(x) == int:
return (x << k) | (x >> (64 - k))
return z3.RotateLeft(x, k)
class xorshift128():
def __init__(self, s0, s1):
self.s = [s0, s1]
def next(self):
s0, s1 = self.s
if type(s0) == int:
result = (s0 + s1) % (2**64)
else:
result = s0 + s1
s1 ^= s0
self.s[0] = rotl(s0, 24) ^ s1 ^ (s1 << z3.BitVecVal(16, 64))
self.s[1] = rotl(s1, 37)
return result
seed = bytes_to_long(os.urandom(8)), bytes_to_long(os.urandom(8))
xor = xorshift128(seed[0], seed[1])
s = z3.Solver()
a, b = z3.BitVec("a", 64), z3.BitVec("b", 64)
xor_bv = xorshift128(a, b)
for i in range(16):
s.add(xor.next() == xor_bv.next())
s.check()
a = int(s.model()[a].as_long())
b = int(s.model()[b].as_long())
print(f"{a = }, {b = }")
print(f"{seed = }")
輸出結果是:
a = 17033195837716372443, b = 16345491342652664975
seed = (17033195837716372443, 16345491342652664975)
可以發現我們將 seed 破解出來了。
z3-solver 是一個可以用指定位數的 BitVec
去進行計算產生一些符號化的變數,我們去對這些變數進行約束的話,資訊夠多且計算單純就可以輕易的獲得解。而如果是一些更特殊的條件下,使用更精確的破解工具是更好的選擇,譬如說只有用到 ^
、&
、<<
、>>
的 LFSR,或者是 MT19937,等同於是在 BitVec
的用代數表示變數,但是可以在
順道一題,python 的 random 是用 MT19937 去實作的,但是 randrange 因為會機率性的丟棄一些東西,造成預測上的困擾,所以我有分享過一篇randrange 的預測,只是程式碼還沒開源,如果對慨念有興趣可以參考看看(當時不知道有 gf2bv 可以用,所以用 z3,後來嘗試了有很大的效率提昇)。
Kuanch
, devarajabc
引入 per-CPU runqueue 和紅黑樹。以虛擬執行時間(virtual runtime)來決定下一個任務要執行什麼。對於不同的優先度,其權重會決定單位時間轉換過去的虛擬執行時間有多少,優先集越高權重越低。對排程器(CFS)來說,它只看得到虛擬執行時間,以這個時間決定公平的跑每個任務。
為什麼它會被取代呢?主要是因為公平性並不一定是排程的全部考量,如果有個需求低延遲的卻較低優先級的任務,那它不應該被丟著一段時間不管。
多考慮了任務的急迫性,引入了 latency-nice,代表此任務有多急,數字越小越急。所以,EEVDF 會比 CFS 有更小的延遲。
Linux 考量很多大型電腦,需要去嚴格考量功耗比。這是一個提供 CPU 排程器評判效率和能量的機制。
有大核和小核,如何考量一個任務要放哪?
首先跟著 linux-kernel-debugging,使用 buildroot 編譯 linux 核心,並且調一些 kernel debug 的設定。
接下來用 qemu 跑起來,開啟它提供的 gdbserver。
接下來用 gdb 的 remote debug 功能連上 gdbserver 就可以開始動態分析。
拿來分析 Linux 核心效能,評價的項目包括: