contributed by < weiso131
>
1
AAAA
: &l
BBBB
: before
CCCC
: &(*p)->next
DDDD
: item->next
2
EEEE
: (*pred_ptr)->r
FFFF
: &(*pred_ptr)->r
remove_free_tree
解讀l == r == NULL
l != NULL || r != NULL
l != NULL && r != NULL
*pred_ptr != (*node_ptr)->l
*pred_ptr == (*node_ptr)->l
github
參考 memory-allocators 的 free list allocator。
這種記憶體分配器屬於通用型分配器,利用一個資料結構(通常是鏈結串列或紅黑樹)紀錄可供使用的記憶體區塊,在使用者請求分配時,在這個資料結構尋找合適的區塊提供給使用者。
另外,若在釋放記憶體時,發現有相鄰的記憶體區塊是可用的,這種實作方法會將其合併。
要以時間複雜度 確認是否能合併,可以使用雙向鏈結串列維護其順序,以方便查詢。
文中提到可以用鍊結串列或是紅黑樹維護,我嘗試使用二元搜尋樹代替紅黑樹,實作這種記憶體分配器。在不發生樹嚴重傾斜的情況下,查詢的時間複雜度與紅黑樹相同,皆是 。 而在測驗題中也有提供相關實作,可直接使用。
至於雙向鏈結串列,在實作過程中發現,若是環狀的鏈結串列,在插入節點時就沒有判斷下一項是否為空指標的問題,因此我使用 list.h 的資料結構與 API 來維護這個雙向鏈結串列。
這些是這個分配器提供給使用者的主要功能
定義在 includes/free_list_alloc.h
void init_fl(size_t size);
void *fl_alloc(size_t size)
void fl_free(void *ptr)
init_fl
fl_alloc
find_block_by_size
fl_free
try_merge
merge_b_to_a
使用 perf 尋找效能瓶頸
(*node_ptr)->size
find_free_tree
使用效能或許有些微提昇,使用 t 檢定驗證
輸出:
3
GGGG
: head->prev=prev
HHHH
: list_entry(pivot,node_t,list)->value
IIII
: list_entry(n,node_t,list)->value
JJJJ
: pivot
KKKK
: right
rebuild_list_link
解說node 不斷往 next
prev 緊跟在 node 後面
當 node 為 NULL 時,代表鍊結串列的最後一個節點找到了
此時 prev 就是最後一個節點
quick_sort
解說稍微跳過幾個步驟
AAAA
: list_first_entry
BBBB
: list_del
CCCC
: list_move_tail
DDDD
: list_add
EEEE
: list_splice
FFFF
: list_splice_tail
list_quicksort
解釋random_shuffle_array
使用 4 個元素的陣列
執行 shuffle 1e6 次
紀錄每種排列的出現次數
再計算卡方值
random_shuffle_array
輸出:
卡方值遠高於
fisher_yates_shuffle
參考 lab0 提供的 shuffle 演算法
輸出:
雖然有減少,但其卡方值仍遠高於
rand
代替 get_unsigned16
輸出:
卡方值小於
無證據說明這個 shuffle 是不公平的
list_move_tail
換為 list_move
無法滿足 stable sortingGGGG
: 14
HHHH
: 2
IIII
: 0
JJJJ
: 3
KKKK
: 2
LLLL
: 1
MMMM
: ~1
NNNN
: 1
PPPP
: 2
clz32
upper
與 lower
upper
是否為 0 , 若不是 0 ,代表 leading zero 在 upper
裡面,對 upper
執行 clz32。反之, leading zero 在 lower , 此時加上 (16 >> c)
正是前方的位元數量。c == 3
, 此時 upper
與 lower
都只剩兩個 bits 的長度,若 upper
不為 0 , 將 upper
帶入 magic
可做最後的計算。反之, +2
然後對 lower
最類似的事情。最後只剩 2 bits , 代表會有四種可能
sqrti
題目中的 sqrti
其實就是 Digit-by-digit Method 的實作,但直接從原本的函式實作解釋它有些困難,我將會提供從最直觀的實作,一步一步優化到現在狀態的過程。
這個實作方法基本上是照著演算法的描述一步一步實現,可以看到他會存在一個乘法運算
這個方法由一個 last
儲存每一次 y
更新時的 m
並在進入判斷式前右移一格
這個調整仍是有效的原因,是因為所有 m
都是 2 的次方
以此改寫公式
再展開
觀察到 可以發現只要對 右移 ,就能得到現在做計算所需的值
這正是 last
在做的事情
last
與 y
在 shift
為 加上一個 m
:
由於到迴圈結束時會被右移 格,對最後的 last
而言相當於每次都加上
這與 y
的功能完全相同
到了這步基本上已經跟原本的 sqrti
相同,只差把每次重新計算的 m
放在迴圈外, 每次 m
往右移兩格的調整
x
在計算過程會被減去 y
的平方
若最後 x
不是 0 ,就代表 x
原本不是完全平方數,需要向上取整,也就是額外 +1
因此,可以對最後的 return
做修改
即可在只使用加減法與位元操作的情況下,完成向上取整的平方根
AAAA
: map->bits
BBBB
: map->bits
CCCC
: first->pprev
DDDD
: n->pprev
EEEE
: n->pprev