# 2020q1 Homework2 (quiz2)
contributed by < [Hsuhaoxiang](https://github.com/Hsuhaoxiang/quiz2) >
> 重新回答[第 2 周測驗題](https://hackmd.io/@sysprog/linux2020-quiz2)
## 了解 xs 的設計
```cpp
typedef union {
/* allow strings up to 15 bytes to stay on the stack
* use the last byte as a null terminator and to store flags
* much like fbstring:
* https://github.com/facebook/folly/blob/master/folly/docs/FBString.md
*/
char data[16];
struct {
uint8_t filler[15],
/* how many free bytes in this stack allocated string
* same idea as fbstring
*/
space_left : 4,
/* if it is on heap, set to 1 */
is_ptr : 1, flag1 : 1, flag2 : 1, flag3 : 1;
};
/* heap allocated */
struct {
char *ptr;
/* supports strings up to 2^54 - 1 bytes */
size_t size : 54,
/* capacity is always a power of 2 (unsigned)-1 */
capacity : 6;
/* the last 4 bits are important flags */
};
} xs;
```
* 首先 `union` 將不同類型的資料放在同一記憶體上,其大小為最大的成員所佔的空間
* `xs` 中每個成員皆小於等於 16 byte `sizeof(xs) = 16`
* `char *data` 為存放短字串的地方
* 第一個 `struct` 中記錄了 `xs` 是長短字串的相關資訊, `filler[15]`佔用15 byte 是為了不讓後面存放資訊位置的地方改寫到短字串 `data` 的內容。
* `space_left` 用了 `struct` 中的 `bit-field` 佔 4 bit ,`is_ptr`, `flag` 等共佔 1 byte
* `space_left` 不紀錄長度,而是紀錄「最大短字串長度減去現在長度」,如此一來當短字串放滿 15 byte 後,最後的 `space_left` 、 `is_ptr` 、 `flag` 剛好會都是 0 也就是 `\0` 不影響短字串的 terminator,沒想到字串結尾的 1 個 byte 藉由 `union` 以及 `bit-field` 還能存下這些資訊!
* 第二個 `struct` 就是存放長字串的地方了 `char* ptr` 佔 8 個 byte , size 放的是長字串所佔空間,扣掉 `null character` 就會都是 2 的指數次方減一,而 `capacity` 則是分配的記憶體大小裡面存的值為對數,所以還要再做指數運算才算得出大小,且保留最後 4-bit 讓 `is_ptr`, `flag` 的值不會被動到。
## xs.c
:::danger
程式列表應該適度分段,從功能探討起,連帶提及答題思維
:notes: jserv
:::
>好的
### xs_new 與 AAA = ?
```cpp
xs *xs_new(xs *x, const void *p)
{
*x = xs_literal_empty();
size_t len = strlen(p) + 1;
if (len > 16) {
x->capacity = ilog2(len) + 1;
x->size = len - 1;
x->is_ptr = true;
x->ptr = malloc((size_t) 1 << x->capacity);
memcpy(x->ptr, p, len);
} else {
memcpy(x->data, p, len);
x->space_left = 15 - (len - 1);
}
return x;
}
```
* `xs_new` 判斷要建立的字串是長字串還是短字串做出對應的操作
* 從 `xs_new` 區分長短字串的判斷與題目 `(len > AAA)` 相同,其中 `len = strlen(p) + 1`
> 答案為 `(c) 16`
### xs_trim
```c
xs *xs_trim(xs *x, const char *trimset)
{
if (!trimset[0])
return x;
char *dataptr = xs_data(x), *orig = dataptr;
/* similar to strspn/strpbrk but it operates on binary data */
uint8_t mask[BBB] = {0};
#define check_bit(byte) (mask[(uint8_t) byte / 8] CCC 1 << (uint8_t) byte % 8)
#define set_bit(byte) (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
}
```
* `xs_trim` 用來去除 `x` 的頭尾在 `trimset` 出現的字元
* 用 `set_bit(x)` 與 `check_bit(x)` 這兩個 macro 對字串頭尾進行切割
* `xs_trim` 是負責去掉頭尾多餘的字串,如果本來是長字串,也就是使用 `ptr` 的字串如果變成小或等於 16 個字串還是會用 `ptr` 而不是 `data[16]` , 並且進行 `xs_concat` 時會因為 `capacity` 讓串接後的字串大小讓串接後的字串大小有問題,必須要再修改!
### BBB = ?
* `uint8_t mask[BBB] = {0}` 中 mask 用於下兩行的 macro `check_bit(byte)` 與 `set_bit(byte)`
* uint_8 最大可到 255 ,`(uint8_t) byte / 8` 最大為 31
> 答案為 `(a) 32`
### CCC = ?
* 利用 `set_bit()` 與 `check_bit()` 來找出需要被修剪的字元,因此 `mask[index]` 中放的是 `trimset` 字元的 ascii code % 8 之後的值,index 為 ascii code 除以 8。
* 了解之後就能發現 `check_bit()` 所要做的運算是 `&`
> 答案為 `(b) &`
### xs_tmp(x)
```cpp
#define xs_tmp(x) \
((void) ((struct { \
_Static_assert(sizeof(x) <= 16, "it is too big"); \
int dummy; \
}){1}), \
xs_new(&xs_literal_empty(), "" x))
```
* 對於 `xs_tmp` 這個 `macro` 我其實我不知道為何需要特別去檢查 `xs_new` 中的 `x` 有沒有小或等於 16 個字元?這樣豈不是讓 `xs` 一開始只能初始為短字串再慢慢合併上去。
:::warning
16 這個數字可變更,但取決於結構體的規劃,請對照看 (https://github.com/facebook/folly/blob/master/folly/FBString.h),並參照 [Cache 原理和實際影響](https://hackmd.io/@sysprog/HkW3Dr1Rb),考慮到 cache line 空間。
:notes: jserv
:::
### xs_grow
```cpp
xs *xs_grow(xs *x, size_t len)
{
if (len <= xs_capacity(x))
return x;
len = ilog2(len) + 1;
if (xs_is_ptr(x))
x->ptr = realloc(x->ptr, (size_t) 1 << len);
else {
char buf[16];
memcpy(buf, x->data, 16);
x->ptr = malloc((size_t) 1 << len);
memcpy(x->ptr, buf, 16);
}
x->is_ptr = true;
x->capacity = len;
return x;
}
```
* 如果 x 佔有的記憶體空間不夠,重新配置一塊 $2^{\lg({len})+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);
if (size + pres + sufs <= capacity) {
memmove(data + pres, data, size);
memcpy(data, pre, pres);
memcpy(data + pres + size, suf, sufs + 1);
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` , `suffix` 字串到 string 的前後, `capacity` 不夠就使用 `xs_grow()`
---
## 延伸問題
### 以上述程式碼為基礎,提供字串複製 (應考慮到 CoW) 和類似 [strtok](http://man7.org/linux/man-pages/man3/strtok.3.html) 功能的函式實作
> 參考 [folly::fbstring](https://github.com/facebook/folly/blob/master/folly/FBString.h) 中使用 CoW 的技巧
----
### COW
#### struct RefCounted
* 先定義一種結構體 `RefCounted` 有兩個成員
* `refCount` 為多少 `xs` 長字串正在參考原本的字串,只有用 `cow_copy()` 時數字才會增加
* `data_` 為字串開頭的記憶體位置,也是存放長字串的地方宣告成長度為 1 的陣列讓 `data_`表示為記憶體位置而不是資料,我有嘗試過把 `data_[1]` 換成 `char *data` 但最後會有問題,還沒找出原因,不過用這種宣告方式是更省記憶體的,比起我省下了一個指標的空間。
```cpp
typedef struct refcnt {
size_t cnt;
char data_[1];
} refcnt_t;
```
:::warning
`refcounted` 這名稱可縮減為 `refcnt`,對應 typedef 後為 `refcnt_t`。上方的 `refCount_` 可改為 `val` 或 `cnt`,因為實質操作存取次數多,簡潔至上
:notes: jserv
:::
>已改
#### getDataOffset
* 求出 `struct` 中 `data_` 位元組偏移值
```cpp
static size_t getDataOffset() {
return offsetof(refcnt_t, data_);
}
```
#### fromData
* 回傳給定字串的 `refcnt_t` 指標
```cpp
static refcnt_t* fromData(char* p) {
return (refcnt_t*)((void*)(size_t*)((void*) p - getDataOffset()));
}
```
#### refs
* 回傳給定字串的 `cnt` 也就是他被參考的次數
```cpp
static size_t refs(char* p) {
return fromData(p)->cnt;
}
```
#### incrementRefs
* 將 `cnt` 加 1
```cpp
static void incrementRefs(char* p) {
fromData(p)->cnt+=1;
}
```
#### decrementRefs
* 將 `cnt` 減 1 , 如果沒有其他指標參考這個 `struct` 則釋放記憶體
```cpp
static void decrementRefs(char* p) {
refcnt_t *dis = fromData(p);
size_t oldcnt = (dis->cnt--);
if (oldcnt == 1) {
free(dis);
}
}
```
#### create
* 當需要建立長字串 ( `xs_new` , `xs_grow` ), 改寫長字串 ( `xs_concat` ) 時分配記憶體給 `refcn_t` 並將回傳 `data_` 這個存放字串的記憶體為
```cpp
static char *create(const char* data, size_t size) {
refcnt_t *result =malloc(getDataOffset() + (size + 1) * sizeof(char));
result->cnt = 0;
memcpy(result->data_, data, strlen(data)+1);
return result->data_;
}
```
#### cow_cpy
```cpp
xs *cow_cpy(xs *dst, xs *src){
if (xs_is_ptr(src)){
dst->is_ptr = true;
dst->ptr = src->ptr;
dst->size = src->size;
dst->capacity = src->capacity;;
incrementRefs(xs_data(src));
}
else{
dst->is_ptr = false;
dst->space_left = src->space_left;
memcpy(dst->data , src->data, xs_size(src));
}
return dst;
}
```
* 對於長字串使用 CoW (copy on write) 這種最佳化手法 , 短字串直接進行 copy
#### [測試](https://github.com/Hsuhaoxiang/quiz2/blob/master/cow_xs.c)
```cpp
int main()
{
xs string = *xs_tmp("()string()");
printf("[%s] : %2zu\n", xs_data(&string), xs_size(&string));
xs_trim(&string, ")(");
printf("\n\n");
printf("trim\n");
printf("[%s] : %2zu\n", xs_data(&string), xs_size(&string));
xs prefix = *xs_tmp("^^^^^^^^"), suffix = *xs_tmp("^^^^^^^^");
xs_concat(&string, &prefix, &suffix);
printf("\n\n");
printf("string concat\n");
printf("[%s] : %2zu\n", xs_data(&string), xs_size(&string));
printf("string refcnt: %ld\n", refs(string.ptr));
printf("\n\n");
printf("cow_cpy\n");
xs copystring = *cow_cpy(&xs_literal_empty(), &string);
printf("string refcnt : %ld\n", refs(string.ptr));
printf("copystring's refcnt : %ld\n", refs(copystring.ptr));
xs_concat(©string, &prefix, &suffix);
printf("\n\n");
printf("copystring concat\n");
printf("string refcnt : %ld\n", refs(string.ptr));
printf("copystring's refcnt : %ld\n", refs(copystring.ptr));
return 0;
}
```
#### 結果
```
[()string()] : 10
trim
[string] : 6
string concat
[^^^^^^^^string^^^^^^^^] : 22
string refcnt: 0
cow_cpy
string refcnt : 1
copystring's refcnt : 1
copystring concat
string refcnt : 0
copystring's refcnt : 0
```
* 符合預期,在進行 `cow_cpy` 之後refcnt會加 1 , 將 copy 來的 string 跟 `prefix` , `suffix` 進行串接表示發生改寫需要進行真正的「複製」,所以refcnt必須減 1
---
### xs_strtok
```cpp
char *xs_strtok(xs *x, const char *delim)
{
char static *nextok;
char *dataptr;
if (!delim[0])
return x;
if (x){
dataptr = xs_data(x);
}
else{
if (!nextok)
return NULL;
dataptr = nextok;
}
/* similar to strspn/strpbrk but it operates on binary data */
uint8_t mask[32] = {0};
//CCC
#define check_bit(byte) (mask[(uint8_t) byte / 8] & (1 << (uint8_t) byte % 8))
#define set_bit(byte) (mask[(uint8_t) byte / 8] |= (1 << (uint8_t) byte % 8))
size_t i,j, slen = strlen(dataptr), trimlen = strlen(delim);
for (i = 0; i < trimlen; i++)
set_bit(delim[i]);
for (i = 0; i < slen; i++)
if (!check_bit(dataptr[i]))
break;
dataptr += i;
slen -= i;
for (j = 0; j < slen; j++)
if (check_bit(dataptr[j]))
break;
*(dataptr + j) = '\0';
nextok = dataptr + j + 1;
if(j + 1 >= slen){
nextok = NULL;
}
return dataptr;
#undef check_bit
#undef set_bit
}
```
:::warning
直接對字串進行改動還沒考慮 cow
:::
#### 測試
```cpp
int main()
{
xs string;
char delim[8] = ", ", *tmp;
xs_new(&string, "If a delimiter byte is found, it is overwritten with a null byte to terminate the current token");
printf("delim: %s\n", delim);
printf("string: %s\n\n", xs_data(&string));
printf("%s\n", xs_strtok(&string, delim));
while(tmp = xs_strtok(NULL, delim))
printf("%s\n", tmp);
return 0;
}
```
#### 結果
```
delim: ,
string: If a delimiter byte is found, it is overwritten with a null byte to terminate the current token
If
a
delimiter
byte
is
found
it
is
overwritten
with
a
null
byte
to
terminate
the
current
token
```
### xs_strtok_r
---
### 設計實驗,確認上述字串處理最佳化 (針對空間效率) 帶來的效益,應考慮到 locality of reference
---
### 在 Linux 核心原始程式碼中,找出類似手法的 SSO 或 CoW 案例並解說