contributed by < 93i7xo2
>
Source: J07: quiz3
建立指定字串的 xs
物件。
相關技巧可參照 hankluo6 的解釋
xs_free
釋放中、長字串在 heap 所佔有的空間,由於儲存的字串可被其他 xs
物件所參照,因此只有在 refcnt = 0
時候才釋放。
xs_cow_lazy_copy
此函式目的是用來為 refcnt >= 2
的長字串分配新的記憶體空間,實現 copy on write。簡單說,修改 xs_copy
複製出後的物件前,必須呼叫此函式複製字串。
為了方便說明假設 x
為 refcnt = 2
的長字串。從 xs_trim
的一小段呼叫開始:
xs_dec_refcnt(x)
將附屬在長字串前的 refcnt
減一,xs_allocate_data
分配新的空間。x->size
有可能低於 LARGE_STRING_LEN
,xs_allocate_data
為其分配中字串的空間,所以後續進行舊字串資料複製時,以 xs_data(x)
取得地址。最後將 *data
指向新字串地址。
原本 *data
所指向的舊空間沒有被釋放,依然被其他 xs
物件所使用。
xs_allocate_data
分配新空間給中、長字串。
常與 xs_new
一併使用
xs_trim
dataptr
與 orig
同樣作為 char *
指向真實字串存放地址 xs_data(x)
。而 Printable characters 的範圍落在 010 0000
~111 1110
,這裡使用類似分頁的技巧,將前 4 bit 視為 index,後 3 bit 對應到 1 << x
的數值,例如 111 1110 => mask[15] = 1 << 6
,利用
將同一 index 數值匯集在同一位元組,預先建表。假設源字串長度 m,欲去除的字集長度 n,這樣的查詢方式相較使用時間複雜度為 的雙層迴圈,只需 。
圖解 20~25 行操作
後續分別對字串紀錄長度。
xs_grow
作為 xs_concat
被呼叫的函式,目的是為 x
物件預留足夠的記憶體空間。is_ptr
的設置要在判斷後。
xs_concat
作為 xs.c
的核心,涵蓋大部分上述的函式呼叫。擴展 string
的空間使其足以容納下 prefix
和 suffix
。
xs_copy
從 xs_tmp
看到,xs
物件的其實在呼叫 xs_new()
前就存在於 caller 的 stack,這點可透過 gdb 觀察 xs_new()
返回的指標的指向和 stack frame 的關係。
同理,xs_free()
的功能也只是在重設 xs
物件的內容,並不會真的去執行 free(x)
,返回的 xs*
也非指向 malloc
新分配出的記憶體空間,而是參數 x
原本所指向的位置。
如果沒有考慮到上述情境,實作出的 xs_copy
可能在離開函式後依然存取 xs_literal_empty()
創立的物件,造成 Undefined behavior。用 cppcheck --enable=all
或是 gcc -fsanitize=undefined
均無法偵測到,而 gcc -O3
會做更深層的分析,勉強可作為靜態分析工具使用。
最後實作如下,仿造 xs_tmp
呼叫時傳入 &xs_literal_empty()
。用 xs_is_large_string
來決定是否分配新的記憶體空間。
xs
v.s. libstdc++gcc >= 5 後的 libstdc++ string 引入 SSO
即便如此,相同 capacity 下 24 bytes 的 xs
或 fbstring
仍然在空間上較 gcc string 少 33%。若將 xs
擴展為 32 bytes 使其容下以 10 進位表示的 64-bit 隨機數或 user ID (fbstring
的使用情境) 的短字串,與 gcc string 進行同樣的讀寫測試(隨機修改陣列中的字串),發現 string
耗時和 cache-misses 次數也較多。
測試環境
測試結果
在 95% 信心水準下,xs
與 string
隨機修改字串所需時間。
觀察兩者的空間使用率
xs
測試中,一開始 stack 上的 5MB 分給 xs arr[131072]=4MB
及 uint32_t* tmp[131072]=1MB
,後續 heap (黃) 512kB 配給 tmp
。string
測試中,heap (橘) 分配給 string
為每個字串 31 bytes,佔用 3.9MB (131072*31 bytes)。回顧 __cstr_data
的最佳化方法,起初定義 4 種型態
接著看合併時的型態轉換
CSTR_STACK_SIZE
,複製到 stack 上預先配置好的空間。CSTR_INTERNING_SIZE
,才儲存於預先配置空間的 interned table,此時字串型態設為 ONSTACK,否則新分配空間的字串型態設為 Otherscstr_grab
複製的字串時才會增加,但缺少如 xs_cow_lazy_copy
的實作。xs
的 large string 有 CoW 的輔助,short string 實作 SSO,適合整合進 interned table 的應是 medium string,因此在 medium string 底下再細分新增一類 interned string,所有跟改變 capacity
相關的程式都需要改寫。
TODO
以 linux/fs/btrfs
為例。F2FS and BtrFS 指出,Btrfs 是以 B-Tree 為基礎,當插入新的節點時,先複製一份父節點,再指向修改後的新的節點,並使用 counter 來紀錄多少節點指向自己,這些 counter 存在於節點之外的 extent-tree。
如果以 refcount
下去搜尋,會發現不少結構裡都有此成員。以實作在 commit 16cdc 的 btrfs_delayed_node
為例:
以 btrfs_get_delayed_node
取得 inode 中的 delayed_node 時會將 refs+1,離開時呼叫 atomic_dec
將 ref-1。
This delayed node is still cached in the btrfs inode, so refs must be > 1 now, and we needn't check it is going to be freed or not.
從註解可猜到,當 ref 歸 0 時應該有對應的函式負責清理。下方 __btrfs_release_delayed_node
的 atomic_dec_and_test
就在目標變數減去 1 為 0 時回傳 true,接著刪除。
commit 16cdc commit message 說明 delayed node 的出現是為了解決 btrfs 頻繁對 b+ tree 插入 inode/directory name item/directory name index,於是使用兩個 list 來維護用來創建或刪除檔案的 delayed node,另一個用來維護等待被 worker 處理的 delayed nodes,在 delay nodes 高於閥值才會增加到 work queue。delayed node 內各有兩個 rbTree,分別管理即將插入或刪除到 b+ tree 的 directory name index 。採用 delayed node 的作法能改善效能。