解釋上述程式碼運作原理:
在主程式中,我們可以看到有四個進入點,分別是將 memory pool 做初始化的 mpool_init()
、配空間的 mpool_alloc
、將空間歸還 memory pool 的 mpool_repool
、以及釋放整個 memory pool 的 mpool_free
。
程式運作的核心概念是根據配置大小對 pool 做分類,在每一個類別 (pool) 中用 array 來區隔每個可配置區、然後用 linked-list 將配置出來 array 中每個元素串成 free list 做配置及釋放(repool)的管理。
struct mpool
程式中,用上列結構來管理 memory pool。其中,cnt
是實際 memory pool 可配置分類區的數量。 pal
則是取 cnt
的 order 2 ceiling,如:若 cnt
為 5,則 pal
為大於等於 5 之最小 2 的冪次方值,8。這和 mpool
的擴充機制有關。
pal
會大於等於 cnt
的原因可以參考 add_pool()
。當使用者需要配置某個大小的空間,而對應的 pool 僅剩下一個可配置區塊(free list 已經指向尾端)時,會透過 mpool_new_pool()
配置一塊完整的 pool,然後再由 add_pool()
將 free list 繼續串下去。而這些新產生的 pool 的管理,就都放在初始索引 cnt
之後的位置中、然後再將 cnt++
。只有在 cnt
撞到頂(cnt == pal
)的時候,透過 realloc
將 pal
配置為原先的兩倍(為 dynamic array 的做法,目的使得尾端的 insertion 和 repool()/alloc()
在找到 i
之後存取可以有 constant time。i
有 constant time 找法,但是 alloc()
沒有這樣做 )。把新產生的 pool 放進 ps[]
中還有的好處是:這樣之後在釋放整個 mpool
時,就不用再去找 mpool_new_pool()
產生的區塊位置,直接將 ps[]
的索引走完就即可。
min_pool
和 max_pool
用來標記 pools 中,能配置給使用者區塊大小的最大及最小值。用來做邊界判斷。
由 ps
、 sizes
、cnt
、pal
所組成的 dynamic array 中,ps
代表的是分別是實際各 pool 的位置、sizes
則是各 pool 中對應的可配置大小,cnt
是當前 array 的尾端、pal
是當前 array 大小的邊界。
hs
在這個結構中,是 flexible array member,大小在初始配置時產生。代表的是各個可配置 pool 的 free list。此 array 的大小在初始(init()
)配置後就是固定的,因為 free list 大小只和可配置區段的範圍有關。
mpool_init()
用來初始化 memory pool、填上 dynamic array 各項初始數值。這裡還沒有實際將可配置區(pools)分配出來。可以看到 pools
、sizes
、hs
都被初始化成零。
mpool_alloc()
透過 mpool_init()
初始化 memory pool 之後,使用者就可以透過 mpool_alloc
向 memory pool 索取空間。
如果是超出 memory pool 所能提供的最大可配置大小,就直接用 mmap()
的方式和系統要空間。
接著,根據 sz
找到對應 pool 的索引值。找尋索引的方式是在每次 iteration 中比較 i
對應到的 pool 可配置空間是否大於所需空間,然後再將可配置空間和 i
遞增。但其實這裡可以先將 sz 取 order 2 ceiling,若前後相等則再將 sz * 2。得到 sz 之後可用 find first set 的方式得到其為 order N 的配置,配合 min_pool
就可以得到 i
找到 i
之後,如果這是使用者第一次配置,那 free list 會是空的。這時透過 mpool_new_pool()
,空間才會真正分配出來、串成 linked-list。然後才把 pool 加進 mpool
當中、並且也將 free list 指向 linked-list 的頭。
當中,linked-list 的串法由 mpool_new_pool()
完成。作法如下:先向系統請求一段連續區塊,然後將連續區段以可配置大小為單位切成數等分。最後在每一等份的頭,把它當作 void *
,寫下下一等份的起始位置。
after 23:04
在 pool 初次配置完成後,*cur
內的值應該會是 free list 中第二個元素。除非該 pool 的可配置大小比 PAGE_SIZE
還要大的時候,那整個 pool 會只有一個元素,此時 (*cur)
會是 NULL
。其他符合此條件的情形有 free list 被拿到只剩最後一個 element 的時候。在這種情形下,會進入配置新 pool 的程式路徑。
配置的方法和第一次配置一樣,只是這個 pool 的相關資訊會透過 add_pool()
,被加在 dynamic array 的尾端。因為 mpool_alloc()
一開始會檢查使用者需要的空間有沒有大於等於 mpool->max_pool
,所以比 max_pool
還要大的配置不會誤入此區。
最後,更新 free list。只要將 free list 的頭指向 linked list 下一個元素的位置即完成更新。
mpool_repool()
將配置出來的空間歸還給 pool。有了以 linked-list 為基礎的 free list,歸還空間的方式就是 list 的 insertion。因此我們只需要找到對應的 free list、把 free list 的頭替換成使用者歸還空間的位置、最後再將該位置起始 sizeof(void *)
的區段,寫成原本 free list 頭的位置,即完成 repool。
因此,理想情況下 YYY 不能用一個表達式完成。因為要做的事包含了找到 i
、以及改寫 ip
起始位置的值。可以用以下方式達成
提出效能和功能的改進策略,並予以實作:
可以將所有「找 i
」的片段做如下更改:
其中,min2
及 max2
用來記錄 min_pool
和 max_pool
二的冪次方
測試環境如下:
使用 perf stat
執行測試,可以看到 branch miss 在改動過後有顯著減少。推測是少了 loop 的重複判斷所致:
解釋上述程式碼運作原理
以 epoll 系統呼叫改寫程式碼,並設計實驗來驗證 proxy 程式碼的效率