# 2022q1 Homework5 (quiz6)
> contributed by < oucs638 >
> 測驗題: [quiz6](https://hackmd.io/@sysprog/linux2022-quiz6)
> Github: [oucs638/linux_2022spring_quiz6](https://github.com/oucs638/linux_2022spring_quiz6)
## 測驗 2 : Lock-free Hash Table
- 此題測驗要補完 `hashmap.c` 中的程式碼,使程式執行符合預期
- 首先看 hash table 在 `hashmap.h` 中的資料結構定義
```c
/* links in the linked lists that each bucket uses */
typedef struct hashmap_keyval {
struct hashmap_keyval *next;
const void *key;
void *value;
} hashmap_kv_t;
/* main hashmap struct with buckets of linked lists */
typedef struct {
hashmap_kv_t **buckets;
uint32_t n_buckets;
uint32_t length; /* total count of entries */
/* pointer to the hash and comparison functions */
uint64_t (*hash)(const void *key);
uint8_t (*cmp)(const void *x, const void *y);
/* custom memory management of internal linked lists */
void *opaque;
hashmap_kv_t *(*create_node)(void *opaque, const void *key, void *data);
void (*destroy_node)(void *opaque, hashmap_kv_t *node);
} hashmap_t;
```
- 每個 `hashmap_t` 會紀錄 `n_buckets` 個 single linked list 的首個節點
- `buckets[i]` 可以取用第 `i` 個 single linked list 的首個節點
- 每個 single linked list 由 `hashmap_kv_t` 組成,`->next` 指向下一個節點
### KKKK, QQQQ
- 這兩個程式碼片段在 `hashmap_put` 函式中
- 當 key 值存在,則要檢查該 key 值的 linked list 是否為 head
- 如是,則新的 key 值接在前一個節點之後,故 `KKKK = &prev->next`
- 如否,則接到 head 之後,故 `QQQQ = &map->buckets[bucket_index]`
#### ZZZZ
- 這個程式碼片段在 `hashmap_del` 函式中
- 當要刪除的 key 值對應的 linked list 存有節點,則要判斷目標節點是否為首個節點
- 如否,則直接修改前一個節點的 `next`,故 `ZZZZ = &prev->next`
### 延伸問題 1
- 解釋上述程式碼運作原理,並指出其設計和實作的缺失 (如原本只接受 uin32_t 型態的輸入,無法處理字串作為 key)
```c=
bool hashmap_put(hashmap_t *map, const void *key, void *value)
{
if (!map)
return NULL;
/* hash to convert key to a bucket index where value would be stored */
uint32_t bucket_index = map->hash(key) % map->n_buckets;
hashmap_kv_t *kv = NULL, *prev = NULL;
/* known head and next entry to add to the list */
hashmap_kv_t *head = NULL, *next = NULL;
while (true) {
/* copy the head of the list before checking entries for equality */
head = map->buckets[bucket_index];
/* find any existing matches to this key */
prev = NULL;
if (head) {
for (kv = head; kv; kv = kv->next) {
if (map->cmp(key, kv->key) == 0)
break;
prev = kv;
}
}
if (kv) { /* if the key exists, update and return it */
if (!next) /* lazy make the next key-value pair to append */
next = map->create_node(map->opaque, key, value);
/* ensure the linked list's existing node chain persists */
next->next = kv->next;
/* CAS-update the reference in the previous node */
if (prev) {
/* replace this link, assuming it has not changed by another
* thread
*/
if (__atomic_compare_exchange(&prev->next, &kv, &next, false,
__ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
/* this node, key and value are never again used by this */
map->destroy_node(map->opaque, kv);
return true;
}
hashmap_put_replace_fail += 1;
} else { /* no previous link, update the head of the list */
/* set the head of the list to be whatever this node points to
* (NULL or other links)
*/
if (__atomic_compare_exchange(&map->buckets[bucket_index], &kv,
&next, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
map->destroy_node(map->opaque, kv);
return true;
}
/* failure means at least one new entry was added, retry the
* whole match/del process
*/
hashmap_put_head_fail += 1;
}
} else { /* if the key does not exist, try adding it */
if (!next) /* make the next key-value pair to append */
next = map->create_node(map->opaque, key, value);
next->next = NULL;
if (head) /* make sure the reference to existing nodes is kept */
next->next = head;
/* prepend the kv-pair or lazy-make the bucket */
if (__atomic_compare_exchange(&map->buckets[bucket_index], &head,
&next, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
__atomic_fetch_add(&map->length, 1, __ATOMIC_SEQ_CST);
return false;
}
/* failure means another thead updated head before this.
* track the CAS failure for tests -- non-atomic to minimize
* thread contention
*/
hashmap_put_retries += 1;
}
}
}
```
- `hashmap_put` 中會先判斷 key value 是否已存在 hash table 中
- 如 key value 已存在 hash table 中,則需要判斷 key 是否存在 head (line 28 - 64)
- 如否,則將 `prev->next` 指向 `next`,將新的節點連接上去 (line 36 - 47)
- 如是,則將新的節點接在 head 之後 (line 48 - 63)
- 如 key value 不存在 hash table 中,則將新的節點作為 head (line 64 - 85)
```c=
bool hashmap_del(hashmap_t *map, const void *key)
{
if (!map)
return false;
uint32_t bucket_index = map->hash(key) % map->n_buckets;
/* try to find a match, loop in case a delete attempt fails */
while (true) {
hashmap_kv_t *match, *prev = NULL;
for (match = map->buckets[bucket_index]; match; match = match->next) {
if ((*map->cmp)(key, match->key) == 0)
break;
prev = match;
}
/* exit if no match was found */
if (!match)
return false;
/* previous means this not the head but a link in the list */
if (prev) { /* try the delete but fail if another thread did delete */
if (__atomic_compare_exchange(&prev->next, &match, &match->next,
false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
__atomic_fetch_sub(&map->length, 1, __ATOMIC_SEQ_CST);
map->destroy_node(map->opaque, match);
return true;
}
hashmap_del_fail += 1;
} else { /* no previous link means this needs to leave empty bucket */
/* copy the next link in the list (may be NULL) to the head */
if (__atomic_compare_exchange(&map->buckets[bucket_index], &match,
&match->next, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
__atomic_fetch_sub(&map->length, 1, __ATOMIC_SEQ_CST);
map->destroy_node(map->opaque, match);
return true;
}
/* failure means whole match/del process needs another attempt */
hashmap_del_fail_new_head += 1;
}
}
return false;
}
```
- `hashmap_del` 中會先判斷 key value 是否已存在 hash table 中
- 如否,則回傳 `false` (line 18 - 19)
- 如是,則判斷目標節點是否為 `head` (line 22 - 43)
- 如否,則將前個節點的 `next` 指向下個節點 (line 22 - 30)
- 如是,則將下個節點設為 `head` (line 31 - 43)
### 延伸問題 2
- 研讀 [lfht](https://github.com/ksandstr/lfht) (Lock-free wait-free hash table using C11 atomics) 程式碼及對應的論文 [Lock-Free Linked Lists and Skip Lists](http://www.cse.yorku.ca/~ruppert/papers/lfll.pdf)
#### Lock-Free Linked Lists and Skip Lists
- 這篇論文討論如何實現 lock-free 的 distributed data structure
- 論文使用 singly-linked list 來實現,並引入 skip list 的結構
- 論文實現的演算法使用 single-word compare & swap synchronization primitive
- 為了實現 lock-free,使個別 process 的 delays 或 failures 不會阻礙其他 process 的執行,論文的實作中使用了 backlinks 和 flag bits 以及 mark 被刪除節點的機制
- backlink 指向被刪除的節點的前一個節點
- 當 flag bit 設為 1,表示下一個節點的刪除正在進行
- backlink 的設計使得被刪除的節點可以復原
- 論文實現的演算法主要有八個部分
- `Search (Key k): Node`
- 如有目標 key 值的節點,則回傳該節點
- 否則,該 key 不存在
- `SearchFrom (Key k, Node *curr_mode): (Node, Node)`
- 回傳兩個相鄰的節點 `n1`, `n2`,符合 `n1.key <= k < n2.key`
- 在拜訪節點的同時,會檢查下個節點是否被 mark,並呼叫 `HelpMarked` 以刪除被 mark 的節點
- `HelpMarked (Node *prev_node, Node *del_node)`
- 嘗試刪除被 mark 的節點 `del_node`,並將前一個節點 `prev_node` 的 flag 設回 0
- 使用 atomic compare and swap 指令進行操作,以確保同時沒有其他 process 對目標節點做更動
- `Delete (Key k): Node`
- 嘗試刪除目標 key 值的節點,使用 three-step deletion
- 首先,呼叫 `SearchFrom` 搜尋目標 key 值節點,如果該節點存在,則呼叫 `TryFlag` 嘗試將前一個節點的 flag 設為 1
- 如果 `TryFlag` 回傳結果不為 `null`,則呼叫 `HelpFlagged` 進行第二步和第三步
- `HelpFlagged (Node *prev_node, Node *del_node)`
- 刪除節點的第二步,將 `del_node` 的 backlink 指向前一個節點,並呼叫 `TryMark` mark `del_node`
- 刪除節點的第三步,呼叫 `HelpMarked` 嘗試刪除目標節點
- `TryMark (Node del_node)`
- 嘗試 mark 要刪除的節點 `del_node`
- 使用 atomic compare ad swap 指令進行操作,以確保同時沒有其他 process 對目標節點做更動
- `TryFlag (Node *prev_node, Node *target_node): (Node, Boolean)`
- 嘗試將要刪除的目標節點的前一個節點 flag 設為 1,並回傳 `prev_node`
- 使用 atomic compare ad swap 指令進行操作,以確保同時沒有其他 process 對目標節點做更動
- 如果目標節點已刪除,則回傳 `null`
- `Insert (Key k, Element e): Node`
- 嘗試插入給定 key 值的新節點
- 呼叫 `SearchForm` 搜尋 key 值插入的位置,並確認 key 值不重複
- 如果要插入的位置前一個節點 flag 為 1,代表後續的節點刪除還未完成,呼叫 `HelpFlagged` 完成刪除動作
- 如果要插入的位置前一個節點被 mark,表示該節點要被刪除,用 backlinks 往前找到沒有 mark 的節點重新設為 `prev_node`
- 使用 atomic compare ad swap 指令進行操作,以確保同時沒有其他 process 對目標節點做更動
### 延伸問題 3
- 參照 [atomic_hash](https://github.com/divfor/atomic_hash) 及其內建的測試程式,用更大規模的來測試