Toast
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Note Insights Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    # 2021q1 Homework3 (quiz3) contributed by < [`toastcheng`](https://github.com/toastcheng) > ###### tags: `linux2021` ## 進度 - [x] 1. 程式碼運作原理 - [x] 2. 改進方案 - [x] 3. 字串複製 (CoW) 的函式實作 - [ ] 4. 分析字串處理最佳化的空間效益 - [x] 5. 整合 string interning - [x] 6. Linux 中的 SSO、CoW 案例 --- ## Q1: Small String Optimization ### 1. 程式碼運作原理 ```cpp typedef union { char data[16]; struct { uint8_t filler[15], space_left : 4, is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1; }; struct { char *ptr; size_t size : 54, capacity : 6; }; } xs; ``` 透過 `xs` 使用 `union` 的方式可以看出 `xs` 總共佔用 16 bytes (128 bits) 的記憶體空間,因為不同情境可能為 `char data[16]` 或另外兩種 `struct`。 #### `xs_data` 回傳字串的開頭 ```cpp static inline char *xs_data(const xs *x) { if (!xs_is_ptr(x)) return (char *) x->data; if (xs_is_large_string(x)) return (char *) (x->ptr + /* OFF */ 4); return (char *) x->ptr; } ``` 從程式的分支邏輯對應 `xs` 的三種 layout,可以看出依照不同情境,字串的開頭可能分別在: 1. short string: `x->data` 指向的位置 字串長度小於 16,直接存放在 stack。 2. large string: `x->ptr` 後 4 個 byte 的位置 字串長度大於 `LARGE_STRING_LEN` (256),會利用前 `x->ptr` 指向位置的前 4 個 byte 存放 reference count,因此真正存放資料的位置在 `x->ptr + 4`。與 `xs_allocate_data` 的邏輯相呼應,其依照字串長度做不同記憶體配置。 3. middle string: `x->ptr` 指向的位置 若是大於或等於 16,並且小於 `LARGE_STRING_LEN`,則一樣分配在 heap,並由 `x->ptr` 指向其位址,與 large string 差別在於不另外紀錄 reference count。 有趣的是 `x->data` 及 `x->ptr` 應是在相同的位置,這個部分應該可以改善,可見 [改進方案](https://hackmd.io/twR8uo8KT7G0rIXbiBfDiQ#2-%E6%94%B9%E9%80%B2%E6%96%B9%E6%A1%88)。 #### `xs_size` 回傳字串的長度 ```cpp static inline size_t xs_size(const xs *x) { return xs_is_ptr(x) ? x->size : 15 - x->space_left; } ``` 從這個函式可以看出字串的長度如何儲存在 `xs`,如果是 short string,其內容完全儲存在 stack,可以由 `space_left` 知道還有多少空間,因此將 `15 - x->space_left` 就能得到大小;而 medium string、large string 因為是動態配置,所以其大小另外存在 `x->size` 中。 #### `xs_capacity` ```cpp static inline size_t xs_capacity(const xs *x) { return xs_is_ptr(x) ? ((size_t) 1 << x->capacity) - 1 : 15; } ``` 如果為 short string,容量固定為 15,而 medium string 及 large string 則透過 `capacity` 紀錄,而 `capacity` 則是在 `xs_new` 中決定的,會分配一個比字串長度還大的 power of 2 為其值。 #### `xs_*_refcnt` ```cpp static inline void xs_set_refcnt(const xs *x, int val) { *((int *) ((size_t) x->ptr)) = val; } static inline void xs_inc_refcnt(const xs *x) { if (xs_is_large_string(x)) ++(*(int *) ((size_t) x->ptr)); } static inline int xs_dec_refcnt(const xs *x) { if (!xs_is_large_string(x)) return 0; return --(*(int *) ((size_t) x->ptr)); } static inline int xs_get_refcnt(const xs *x) { if (!xs_is_large_string(x)) return 0; return *(int *) ((size_t) x->ptr); } ``` 對長字串的 reference count 做操作的函式,可以發現唯獨 `xs_set_refcnt` 並沒有確保字串為長字串,可見[改進方案](https://hackmd.io/twR8uo8KT7G0rIXbiBfDiQ#2-%E6%94%B9%E9%80%B2%E6%96%B9%E6%A1%88)。 #### `ilog2` 取對數 ```cpp static inline int ilog2(uint32_t n) { return /* LLL */ 32 - __builtin_clz(n) - 1; } ``` 透過 `__builtin_clz` 的使用,找到最高位非 0 bit 的位置(從右算起),根據對數的定義該位置 - 1 便是 `n` 的對數的整數部分。 #### `xs_allocate_data` 配置記憶體 ```cpp static void xs_allocate_data(xs *x, size_t len, bool reallocate) { /* Medium string */ if (len < LARGE_STRING_LEN) { x->ptr = reallocate ? realloc(x->ptr, (size_t) 1 << x->capacity) : malloc((size_t) 1 << x->capacity); return; } /* Large string */ x->is_large_string = 1; /* The extra 4 bytes are used to store the reference count */ x->ptr = reallocate ? realloc(x->ptr, (size_t)(1 << x->capacity) + 4) : malloc((size_t)(1 << x->capacity) + 4); xs_set_refcnt(x, 1); } ``` `xs_allocate_data` 針對 medium、large string 做記憶體配置,在此判斷如果長度大於 `LARGE_STRING_LEN` 就要多配置 4 個 bytes 以供紀錄 reference count。 #### `xs_new` ```cpp xs *xs_new(xs *x, const void *p) { *x = xs_literal_empty(); size_t len = strlen(p) + 1; if (len > /* NNN */ 16) { x->capacity = ilog2(len) + 1; x->size = len - 1; x->is_ptr = true; xs_allocate_data(x, x->size, 0); memcpy(xs_data(x), p, len); } else { memcpy(x->data, p, len); x->space_left = 15 - (len - 1); } return x; } ``` `xs_new` 初始化一個 `xs`,針對是否是 short string 決定是否要利用 `xs_allocate_data` 動態配置記憶體,在 `xs_allocate_data` 中會再針對 medium string 或 large string 做不同的配置。 #### `xs_grow` ```cpp xs *xs_grow(xs *x, size_t len) { char buf[16]; if (len <= xs_capacity(x)) return x; /* Backup first */ if (!xs_is_ptr(x)) memcpy(buf, x->data, 16); x->is_ptr = true; x->capacity = ilog2(len) + 1; if (xs_is_ptr(x)) { xs_allocate_data(x, len, 1); } else { xs_allocate_data(x, len, 0); memcpy(xs_data(x), buf, 16); } return x; } ``` 增加 `xs` 所配置的記憶體空間至 `len`,並更新 `capacity`,並且處理字串種類的變化,但只涵蓋從 short string 成長為 medium string 的情況,仔細觀察也會發現若是成長為 large string,並沒有相對應的處理,改進可見[改進方案](https://hackmd.io/twR8uo8KT7G0rIXbiBfDiQ#2-%E6%94%B9%E9%80%B2%E6%96%B9%E6%A1%88)。 #### `xs_cow_lazy_copy` ```cpp static bool xs_cow_lazy_copy(xs *x, char **data) { if (xs_get_refcnt(x) <= 1) return false; /* Lazy copy */ xs_dec_refcnt(x); xs_allocate_data(x, x->size, 0); if (data) { memcpy(xs_data(x), *data, x->size); /* Update the newly allocated pointer */ *data = xs_data(x); } return true; } ``` 這個函式意義上應是將字串 `data` 的內容複製到 `x`,先考慮 reference count > 1 的情況: 1. 相同內容的長字串因為共用同一段記憶體空間,因此當要將 `data` 的內容更改為 `x`,勢必無法繼續共用原本的記憶體空間,因此在更改之前呼叫 `xs_dec_refcnt`(`x` 不再引用到這個記憶體空間) 2. 呼叫 `xs_allocate_data` 重新分配記憶體空間 3. 利用 `memcpy` 進行複製 而如果 reference count <= 1, 在最初的檢查 `x` 會直接回傳 false 並且完全沒有執行到 `memcpy`,亦即沒有真的進行複製。對於這樣的行為思考許久,為什麼不必複製? 搜尋使用的情境發現呼叫 `xs_cow_lazy_copy` 的方式都是對相同的內容進行複製,共有兩處。 `xs_concat`: ```cpp char *data = xs_data(string); xs_cow_lazy_copy(string, &data); ``` 以及 `xs_trim`: ```cpp char *dataptr = xs_data(x), *orig = dataptr; if (xs_cow_lazy_copy(x, &dataptr)) orig = dataptr; ``` 或許這兩處使用 `xs_cow_lazy_copy` 是因為它們將要改動原本的內容,因此先複製到別的位置。但 `xs_cow_lazy_copy` 較符合預期的行為應該還是要複製 reference count <= 1 的內容。 #### `xs_concat` ```cpp xs *xs_concat(xs *string, const xs *prefix, const xs *suffix) { size_t pres = xs_size(prefix), sufs = xs_size(suffix), size = xs_size(string), capacity = xs_capacity(string); char *pre = xs_data(prefix), *suf = xs_data(suffix), *data = xs_data(string); xs_cow_lazy_copy(string, &data); if (size + pres + sufs <= capacity) { memmove(data + pres, data, size); memcpy(data, pre, pres); memcpy(data + pres + size, suf, sufs + 1); if (xs_is_ptr(string)) string->size = size + pres + sufs; else string->space_left = 15 - (size + pres + sufs); } else { xs tmps = xs_literal_empty(); xs_grow(&tmps, size + pres + sufs); char *tmpdata = xs_data(&tmps); memcpy(tmpdata + pres, data, size); memcpy(tmpdata, pre, pres); memcpy(tmpdata + pres + size, suf, sufs + 1); xs_free(string); *string = tmps; string->size = size + pres + sufs; } return string; } ``` 回傳一個 `prefix + string + suffix` 的字串。 #### `xs_trim` ```cpp xs *xs_trim(xs *x, const char *trimset) { if (!trimset[0]) return x; char *dataptr = xs_data(x), *orig = dataptr; if (xs_cow_lazy_copy(x, &dataptr)) orig = dataptr; /* similar to strspn/strpbrk but it operates on binary data */ uint8_t mask[32] = {0}; #define check_bit(byte) (/* CCC */ mask[(uint8_t) byte / 8] & 1 << (uint8_t) byte % 8) #define set_bit(byte) (/* SSS */ mask[(uint8_t) byte / 8] |= 1 << (uint8_t) byte % 8) size_t i, slen = xs_size(x), trimlen = strlen(trimset); for (i = 0; i < trimlen; i++) set_bit(trimset[i]); for (i = 0; i < slen; i++) if (!check_bit(dataptr[i])) break; for (; slen > 0; slen--) if (!check_bit(dataptr[slen - 1])) break; dataptr += i; slen -= i; /* reserved space as a buffer on the heap. * Do not reallocate immediately. Instead, reuse it as possible. * Do not shrink to in place if < 16 bytes. */ memmove(orig, dataptr, slen); /* do not dirty memory unless it is needed */ if (orig[slen]) orig[slen] = 0; if (xs_is_ptr(x)) x->size = slen; else x->space_left = 15 - slen; return x; #undef check_bit #undef set_bit } ``` 將 `x` 開頭及結尾含有 `trimset` 內的字元的部分截去。 ### 2. 改進方案 #### union 中多餘的 data type 程式碼中可以看到 `xs` 所代表的字串有分為短中長三種,但這三種並不是一一對應 `xs` 的三種排列,短字串對應的是: ```cpp struct { uint8_t filler[15], space_left : 4, is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1; }; ``` 長字串則是: ```cpp struct { char *ptr; size_t size : 54, capacity : 6; }; ``` 可以發現 `char data[16];` 是多餘的,其功能可以由 `filler` 代替,因為它們指向相同的記憶體位址,雖然 `filler` 的大小為 15 個 bytes,但當字串長度包含 `\0` 為 16 時,`space_left` 為 0、後面 4 個 flag 也因為是 short string 而為 0,因此 `filler` 後的最後一個 byte 也會是 `\0`。 但這樣設計要成立需要確保編譯器沒有為了避免 unaligned memory access 做 padding。 根據 ISO/IEC 9899:201x 6.7.2.1 p.113 > If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit 以 1 word = 4 byte 來說,`filler` 佔據了 3 個 word 又 3 bytes,最後一個 word 恰好可以讓剩下的 bit fields 補齊,因此沒有 padding 發生。 ```cpp struct { uint8_t filler[15], space_left : 4, is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1; }; ``` 做一個實驗來看看每個 bitfield 的位置是否不必加額外的 pack 也不會有多餘的 padding: ```cpp typedef struct { uint8_t filler[15], space_left : 4, is_ptr : 1, is_large_string : 1, flag2 : 1, flag3 : 1; } s_t; int main() { s_t s; printf("filler: %p\n", &s.filler); printf("space_left: %p\n", s.space_left); printf("is_ptr: %p\n", &s.is_ptr); printf("is_large_string: %p\n", &s.is_large_string); printf("flag2: %p\n", &s.flag2); printf("flag3: %p\n", &s.flag3); } ``` 但很顯然 bit field 無法被 address of: ```shell padding_test.c:17:42: error: cannot take address of bit-field 'space_left' 17 | printf("space_left: %p\n", ((char *) &s.space_left)); | ^ ``` 因為 bit field 的單位是 bit,比指標指向的單位 (byte) 還要細。 在 ISO/IEC 9899:201x 6.7.2.1 p.112 提到: > The unary & (address-of) operator cannot be applied to a bit-field object; thus, there are no pointers to or arrays of bit-field objects. 所以改由觀察整個 struct 所佔據的記憶體空間,使用 `sizeof` 來看是否大於 16: ```cpp int main() { printf("size: %lu\n", sizeof(s_t)); } ``` > 預期輸出: > ``` > size: 16 > ``` 結果顯示確實沒有額外的 padding。 參考資料 [Unaligned Memory Accesses](https://www.kernel.org/doc/Documentation/unaligned-memory-access.txt#:~:text=Unaligned%20memory%20accesses%20occur%20when,be%20an%20unaligned%20memory%20access.) #### `xs_set_refcnt` checking `xs_set_refcnt` 並沒有防止使用者對 short string、middle string 指派 reference count。 ```cpp static inline void xs_set_refcnt(const xs *x, int val) { if (xs_is_large_string(x)) *((int *) ((size_t) x->ptr)) = val; } ``` #### `xs_allocate_data` 考慮將已分配為 large string 重新分配成 medium string 的長度,應把 `is_large_string` 這個 flag 設為 0。 ```cpp static void xs_allocate_data(xs *x, size_t len, bool reallocate) { /* Medium string */ if (len < LARGE_STRING_LEN) { x->ptr = reallocate ? realloc(x->ptr, (size_t) 1 << x->capacity) : malloc((size_t) 1 << x->capacity); x->is_large_string = 0; return; } ``` #### `xs_grow` 邏輯錯誤 `xs_grow` 有許多明顯的問題: 1. 在 short string 成長為 medium/large string 的情況,先更改了 `is_ptr` 才在後續判斷原本是否為 short string (利用 `xs_is_ptr`) 2. 沒有考慮增長為 large string 的情況 用一個簡單的測試就能看到程式的問題: ```cpp int main(int argc, char *argv[]) { xs *string = xs_new(&xs_literal_empty(), "12345"); printf("type should be short (0): %d, capcity: %ld\n", xs_type(string), xs_capacity(string)); xs_grow(string, 16); printf("type should be medium (1): %d, capcity: %ld\n", xs_type(string), xs_capacity(string)); xs_grow(string, 256); printf("type should be large (2): %d, capcity: %ld\n", xs_type(string), xs_capacity(string)); return 0; } ``` > 預期輸出: > ```shell > type should be short (0): 0, capcity: 15 > Segmentation fault > ``` 修改程式碼如下: ```cpp xs *xs_grow(xs *x, size_t len) { // 將成長前的 is_ptr 保存起來 bool is_ptr = x->is_ptr; x->is_ptr = true; if (len < LARGE_STRING_LEN) { if (is_ptr) { xs_allocate_data(x, len, 1); } else { xs_allocate_data(x, len, 0); memcpy(xs_data(x), buf, 16); } } else { // 增長為 large string 的情況 x->is_large_string = true; if (is_ptr) { xs_allocate_data(x, len, 1); } else { xs_allocate_data(x, len, 0); memcpy(xs_data(x) + 4, buf, 16); } } return x; } ``` > 預期輸出: > ```shell > type should be short (0): 0, capcity: 15 > type should be medium (1): 1, capcity: 31 > type should be large (2): 2, capcity: 511 > ``` ### 3. 字串複製 (CoW) 的函式實作 參照 [5. 整合 string interning](https://hackmd.io/twR8uo8KT7G0rIXbiBfDiQ#5-%E6%95%B4%E5%90%88-string-interning) 的部分,將 `xs_cow_lazy_copy` 改寫,主要需要考量若 `x` 為 large string,那複製 `data` 到 `x` 時,必須一併處理 reference count,在複製前,應將 reference count 減一,然後再為 `x` 配置新的記憶體空間。 ```cpp static void xs_cow_lazy_copy(xs *x, char **data) { if (!x->is_large_string) { memcpy(xs_data(x), *data, strlen(*data)); return; } else if (xs_get_refcnt(x) > 1) { /* Lazy copy */ xs_dec_refcnt(x); xs_interning_nontrack(x, *data, x->size, 0); } if (data) { memcpy(xs_data(x), *data, x->size); } /* Update the newly allocated pointer */ *data = xs_data(x); return; } ``` 考量到在 string interning 中的實作,會對比字串的 hash 是否跟 pool 中存在的 string 相同,若有則進一步比對,若確定與現存的 string 重複,則使用同一段記憶體空間。 但在 `xs_cow_lazy_copy` 的使用情境不同,即便字串相同,但可能即將被 `concat` 或 `trim` 等方式修改內容,所以會希望它被存在不同的記憶體空間,即使內容重複,因此實作 `xs_interning_nontrack`,將記憶體配置,而不透過 string interning 紀錄其值,也不會計算 hash。 ```cpp static void xs_interning_nontrack(xs *x, const void *p, size_t len, bool reallocate) { x->ptr = reallocate ? realloc(x->ptr, (size_t) 1 << x->capacity + 4) : malloc((size_t) 1 << x->capacity + 4); memcpy(xs_data(x), p, len); xs_set_refcnt(x, 1); } ``` 而在 `concat` 及 `trim` 完成對字串的更動之後,加上這一個操作,將該記憶體位置加入 string interning 的紀錄。 ```cpp if (string->is_large_string) add_interning_address(string->ptr); ``` `add_interning_address` 的實作如下,跟 `add_interning` 雷同,差別在於它直接接受一個位於 `heap` 的字串位址,不必再分配記憶體,而 `add_interning` 則是需要自行配置記憶體,再將該記憶體位址回傳給呼叫的函式,該函式需要負責將位址指派到 `xs_data(xs)`。 ```cpp void add_interning_address(char *data) { XS_LOCK(); int len = strlen(data); uint32_t hash = hash_blob(data, len); if (!__intern_ctx.pool) { __intern_ctx.pool = malloc(sizeof(struct __xs_pool)); } struct xs_node **n = &__intern_ctx.pool->node[hash]; while (*n) { if (strcmp((*n)->data + 4, data) == 0) { ++(*(int *) ((size_t) (*n)->data)); XS_UNLOCK(); return; } n = &((*n)->next); } *n = malloc(sizeof(struct xs_node)); (*n)->data = data; XS_UNLOCK(); return; } ``` ### 4. 分析字串處理最佳化的空間效益 :::success 應考慮到 locality of reference,善用 GDB 追蹤和分析,確認字串的確符合預期地配置於 stack 或 heap; ::: `// TODO 補完分析和實驗` #### `xs` 空間效率 針對 1000 個 short+medium 的字串做初始化: `xs` ``` KB 46.84^ # | @@# | @@::@@# | @@@@ : @@# | @@@@@ @ : @@# | :@@ @@@ @ : @@# | :::@:@@ @@@ @ : @@# | @@@: :@:@@ @@@ @ : @@# | @@@@@@ : :@:@@ @@@ @ : @@# | @@@ @@@@ : :@:@@ @@@ @ : @@# | @@@@ @ @@@@ : :@:@@ @@@ @ : @@# | :@@@@ @ @ @@@@ : :@:@@ @@@ @ : @@# | @@:@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | @@::@ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | @@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | @@@@@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | @@@@@ @@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | :: @@@@@@@ @@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | : ::@ @@@@@ @@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# | : ::@ @@@@@ @@:@ : @ :@ @@ @ @ @@@@ : :@:@@ @@@ @ : @@# 0 +----------------------------------------------------------------------->ki 0 486.2 ``` `std::string` ``` KB 77.54^ # | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: | #:::::::@:: |@ #:::::::@:: 0 +----------------------------------------------------------------------->Mi 0 2.430 ``` ### 5. 整合 string interning 嘗試用簡易的方式來追蹤程式中的 large string,加入 `intern.[ch]` 來為 `xs` 擴充。 大部分的 struct 為 `cstr` 的簡化: ```cpp struct xs_node { char *data; struct xs_node *next; }; struct __xs_interning { int lock; struct __xs_pool *pool; }; struct __xs_pool { struct xs_node *node[INTERNING_POOL_SIZE]; }; static struct __xs_interning __intern_ctx; ``` 實作 `add_interning`,該函式主要會被 `xs_new` 呼叫,若是使用者新增了一個 large string,便會透過此函式分配記憶體,並計算 hash,放進 pool 中。 ```cpp struct xs_node *add_interning(const char *str) { XS_LOCK(); int len = strlen(str); uint32_t hash = hash_blob(str, len); if (!__intern_ctx.pool) { __intern_ctx.pool = malloc(sizeof(struct __xs_pool)); } struct xs_node **n = &__intern_ctx.pool->node[hash]; while (*n) { if (strcmp((*n)->data + 4, str) == 0) { ++(*(int *) ((size_t) (*n)->data)); XS_UNLOCK(); return *n; } n = &((*n)->next); } *n = malloc(sizeof(struct xs_node)); (*n)->data = malloc(sizeof(char) * len + 4); memcpy(((*n)->data + 4), str, len); XS_UNLOCK(); return (*n); } ``` 修改過後的 `xs_new`,直接負責處理不同長度字串的邏輯,而 `xs_allocate_data` 也簡化為處理 middle string 的記憶體分配: ```cpp xs *xs_new(xs *x, const void *p) { *x = xs_literal_empty(); size_t len = strlen(p) + 1; if (len > LARGE_STRING_LEN) { x->capacity = ilog2(len) + 1; x->size = len - 1; x->is_ptr = true; x->is_large_string = true; xs_interning(x, p, x->size, 0); } else if (len > /* NNN */ MIDDLE_STRING_LEN) { x->capacity = ilog2(len) + 1; x->size = len - 1; x->is_ptr = true; xs_allocate_data(x, x->size, 0); memcpy(xs_data(x), p, len); } else { memcpy(x->data, p, len); x->space_left = 15 - (len - 1); } return x; } ``` large string 的記憶體分配以及 string interning 紀錄則改由 `xs_interning` 來負責: ```cpp static void xs_interning(xs *x, const void *p, size_t len, bool reallocate) { // the allocation and set refcnt is handled at interning struct xs_node *n = add_interning(p); x->ptr = n->data; } ``` 作為測試,新增三個長度超過 `LARGE_STRING_LEN` 的相同字串,並用 `xs_data` (效果同 `string->ptr + 4`)並用 `%p` 觀察是否有共用同一段記憶體位址。 ```cpp #include "../xs.h" int main(int argc, char *argv[]) { xs *string1 = xs_new(&xs_literal_empty(), "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); xs *string2 = xs_new(&xs_literal_empty(), "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); xs *string3 = xs_new(&xs_literal_empty(), "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"); printf("string1: %p\n", xs_data(string1)); printf("string2: %p\n", xs_data(string2)); printf("string3: %p\n", xs_data(string3)); } ``` > 預期輸出: > ``` > string1: 0x55a42c2332d4 > string2: 0x55a42c2332d4 > string3: 0x55a42c2332d4 > ``` 詳細實作可見 [GitHub](https://github.com/ToastCheng/linux-2021q1/blob/main/week3/q3/intern.c)(TODO: 優化實作)。 ### 6. Linux 中的 SSO、CoW 案例 CoW 的設計應該會有兩個情境: 1. 不修改內容時,**不複製**使用共享的資料 2. 預期會修改內容時,複製資料至新分配的記憶體空間 觀察 `sk_buff`,為代表 socket buffer 的 struct,有對應的函式 `skb_copy`、`skb_clone`: #### `skb_copy` ```cpp /** * skb_copy - create private copy of an sk_buff * @skb: buffer to copy * @gfp_mask: allocation priority * * Make a copy of both an &sk_buff and its data. This is used when the * caller wishes to modify the data and needs a private copy of the * data to alter. Returns %NULL on failure or the pointer to the buffer * on success. The returned buffer has a reference count of 1. * * As by-product this function converts non-linear &sk_buff to linear * one, so that &sk_buff becomes completely private and caller is allowed * to modify all the data of returned buffer. This means that this * function is not recommended for use in circumstances when only * header is going to be modified. Use pskb_copy() instead. */ struct sk_buff *skb_copy(const struct sk_buff *skb, gfp_t gfp_mask) { int headerlen = skb_headroom(skb); unsigned int size = skb_end_offset(skb) + skb->data_len; struct sk_buff *n = __alloc_skb(size, gfp_mask, skb_alloc_rx_flag(skb), NUMA_NO_NODE); if (!n) return NULL; /* Set the data pointer */ skb_reserve(n, headerlen); /* Set the tail pointer and length */ skb_put(n, skb->len); BUG_ON(skb_copy_bits(skb, -headerlen, n->head, headerlen + skb->len)); skb_copy_header(n, skb); return n; } ``` 註解提到: > Make a copy of both an &sk_buff and its data. This is used when the caller wishes to modify the data and needs a private copy of the data to alter. 很明顯地這是 CoW 的情境,在預期會修改內容的情況複製,並將 reference count 設為 1。 #### `skb_clone` ```cpp /** * skb_clone - duplicate an sk_buff * @skb: buffer to clone * @gfp_mask: allocation priority * * Duplicate an &sk_buff. The new one is not owned by a socket. Both * copies share the same packet data but not structure. The new * buffer has a reference count of 1. If the allocation fails the * function returns %NULL otherwise the new buffer is returned. * * If this function is called from an interrupt gfp_mask() must be * %GFP_ATOMIC. */ struct sk_buff *skb_clone(struct sk_buff *skb, gfp_t gfp_mask) { struct sk_buff_fclones *fclones = container_of(skb, struct sk_buff_fclones, skb1); struct sk_buff *n; if (skb_orphan_frags(skb, gfp_mask)) return NULL; if (skb->fclone == SKB_FCLONE_ORIG && refcount_read(&fclones->fclone_ref) == 1) { n = &fclones->skb2; refcount_set(&fclones->fclone_ref, 2); } else { if (skb_pfmemalloc(skb)) gfp_mask |= __GFP_MEMALLOC; n = kmem_cache_alloc(skbuff_head_cache, gfp_mask); if (!n) return NULL; n->fclone = SKB_FCLONE_UNAVAILABLE; } return __skb_clone(n, skb); } ``` 註解提到: > The new one is not owned by a socket. Both copies share the same packet data but not structure. The new buffer has a reference count of 1. 透過 `skb_clone` 複製的 socket buffer 會跟原本的 `sk_buffer` 共享 packet data (`unsigned char *data`),並將 reference count 設為 1,可見 `__skb_clone`,在 `sk_buff` 中 reference count 紀錄在 `refcount_t users` 中: ```cpp static struct sk_buff *__skb_clone(struct sk_buff *n, struct sk_buff *skb) { // ... refcount_set(&n->users, 1); // ... } ``` 這和純粹的共享資料有點不同,可以注意到 `skb_clone` 依然將 reference count 設為 1,照 string interning 的邏輯,如果要重複使用相同的字串,應該要將 reference count 加一。 那增加 reference count 的情境應該是本來預期的共享資料,因此用 `refcount_inc\(&[a-zA-Z]+->users\)` 搜尋,但並沒有特定的函式負責管理,而是每個函式在接收到 `sk_buff` 的指標副本後負責增減 reference count,各自管理。 #### `skb_unshare` ```cpp /* * Copy shared buffers into a new sk_buff. We effectively do COW on * packets to handle cases where we have a local reader and forward * and a couple of other messy ones. The normal one is tcpdumping * a packet thats being forwarded. */ /** * skb_unshare - make a copy of a shared buffer * @skb: buffer to check * @pri: priority for memory allocation * * If the socket buffer is a clone then this function creates a new * copy of the data, drops a reference count on the old copy and returns * the new copy with the reference count at 1. If the buffer is not a clone * the original buffer is returned. When called with a spinlock held or * from interrupt state @pri must be %GFP_ATOMIC * * %NULL is returned on a memory allocation failure. */ static inline struct sk_buff *skb_unshare(struct sk_buff *skb, gfp_t pri) { might_sleep_if(gfpflags_allow_blocking(pri)); if (skb_cloned(skb)) { struct sk_buff *nskb = skb_copy(skb, pri); /* Free our shared copy */ if (likely(nskb)) consume_skb(skb); else kfree_skb(skb); skb = nskb; } return skb; } ``` 另外還有 `skb_unshare` 這樣的函式,先用 `skb_cloned` 來判斷拿到的 `skb` 是否為透過 `skb_clone` 得到的,如果是就重新呼叫 `skb_copy` 斷開兩者共享的 packet data。

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully