# 2021q1 Homework1 (lab0)
contributed by < `ccs100203` >
###### tags: `linux2021`
## 實驗環境
```shell
$ gcc --version
gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 8
On-line CPU(s) list: 0-7
Thread(s) per core: 2
Core(s) per socket: 4
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 158
Model name: Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz
Stepping: 9
CPU MHz: 2635.475
CPU max MHz: 3800.0000
CPU min MHz: 800.0000
BogoMIPS: 5599.85
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 6144K
NUMA node0 CPU(s): 0-7
```
---
## 作業要求
> [lab0](https://hackmd.io/@sysprog/2020-lab0)
詳細閱讀 [C Programming Lab ](http://www.cs.cmu.edu/~213/labs/cprogramminglab.pdf),依據指示著手修改 queue.[ch] 和連帶的檔案,測試後用 Git 管理各項修改,記得也該實作 q_sort 函式。
- q_new: 創一個空的 queue
- q_free: 釋放掉一個 queue
- q_insert_head: 在 head 插入一個 element (LIFO)
- q_insert_tail: 在 tail 插入一個 element (FIFO), O(1)
- q_remove_head: 把 head 的 element 移除
- q_size: return queue 的大小
- q_reverse: 將 queue 反轉,不配置或釋放任何的 element
- q_sort: 將 queue 由小排到大, sort by nature sort
---
## 開發過程
### q_new
檢查 malloc 是否正常運作
```cpp
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
// if malloc return NULL
if (q == NULL)
return NULL;
q->head = NULL;
q->tail = NULL;
q->size = 0;
return q;
}
```
### q_free
要記得先清掉 value
```cpp
void q_free(queue_t *q)
{
// NULL queue
if (q == NULL)
return;
for (list_ele_t *tmp = q->head; q->head; tmp = q->head) {
q->head = q->head->next;
free(tmp->value);
free(tmp);
}
free(q);
}
```
### q_insert_head
一開始在檢查 malloc 時沒有 `free(newh)`,導致 `make test` 時有記憶體未釋放,然後沒有做 `memset` 的話,會遇到 char* 裡頭塞了怪怪的東西,所以後來就先把他清空了。
```cpp
bool q_insert_head(queue_t *q, char *s)
{
// NULL queue
if (q == NULL)
return false;
list_ele_t *newh;
newh = malloc(sizeof(list_ele_t));
// if malloc fail
if (newh == NULL)
return false;
newh->value = malloc((strlen(s) + 1) * sizeof(char));
// if malloc fail
if (newh->value == NULL) {
free(newh);
return false;
}
memset(newh->value, 0, (strlen(s) + 1) * sizeof(char));
strncpy(newh->value, s, strlen(s));
newh->next = q->head;
q->head = newh;
q->size++;
if (q->size == 1)
q->tail = newh;
return true;
}
```
### q_insert_tail
跟 insert head 滿像的
```cpp
bool q_insert_tail(queue_t *q, char *s)
{
// NULL queue
if (q == NULL)
return false;
list_ele_t *newt;
newt = malloc(sizeof(list_ele_t));
// if malloc fail
if (!newt)
return false;
newt->value = malloc((strlen(s) + 1) * sizeof(char));
// if malloc fail
if (newt->value == NULL) {
free(newt);
return false;
}
memset(newt->value, 0, (strlen(s) + 1) * sizeof(char));
strncpy(newt->value, s, strlen(s));
newt->next = NULL;
// if queue is empty
if (q->size == 0) {
q->head = newt;
q->tail = newt;
} else {
q->tail->next = newt;
q->tail = newt;
}
q->size++;
return true;
}
```
### q_remove_head
照著註解的方式去做,一開始 `q->size` 忘記扣,導致 size 錯誤
```cpp
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
// NULL or empty queue
if (q == NULL || q->head == NULL)
return false;
if (sp != NULL && q->head->value) {
memset(sp, 0, bufsize);
strncpy(sp, q->head->value, bufsize - 1);
}
list_ele_t *tmp = q->head;
q->head = q->head->next;
free(tmp->value);
free(tmp);
q->size--;
return true;
}
```
### q_size
確認是否為 NULL queue
```cpp
int q_size(queue_t *q)
{
return (q == NULL) ? 0 : q->size;
}
```
### q_reverse
用了 `prev` `curr` `next` 三個指標去紀錄目前反轉的位置,一路從頭做到尾
```cpp
void q_reverse(queue_t *q)
{
// NULL queue or element < 2
if (q == NULL || q->size < 2)
return;
list_ele_t *prev, *next, *curr;
prev = NULL;
next = q->head->next;
curr = q->head;
while (curr != NULL) {
curr->next = prev;
prev = curr;
curr = next;
if (next != NULL)
next = next->next;
}
curr = q->head;
q->head = q->tail;
q->tail = curr;
}
```
探索是否有其他作法
TODO
### q_sort
參照 [Linkd List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 的 `merge sort` 做更改,起初使用遞迴版本時遇到 stack 爆掉的問題,後來用 iterate 的版本時遇到了不能 `malloc` 的問題,於是又更改了一下寫法才能通過。
在舊版的寫法中,會遇到一開始 `head` 不知道要放什麼,所以需要在 5~13 行去提前判斷 `head` 應該要放什麼,在新版中改為用一個 pointer to pointer `tmp` 去指著 `head`,就可以在做 while 時把 node 直接放進 `tmp` 並同時把第一個 node 給 `head`,隨後只需要把 `tmp` 一直往後接就可以把 list 串起來。
#### 舊版
```cpp=
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
list_ele_t *temp = NULL, *q = NULL;
// init the nodes
if (strnatcmp(l1->value, l2->value) < 0) {
q = l1;
temp = l1;
l1 = l1->next;
} else {
q = l2;
temp = l2;
l2 = l2->next;
}
// sort and connect two list
while (l1 && l2) {
if (strnatcmp(l1->value, l2->value) < 0) {
temp->next = l1;
temp = temp->next;
l1 = l1->next;
} else {
temp->next = l2;
temp = temp->next;
l2 = l2->next;
}
}
// connect remaining node
if (l1)
temp->next = l1;
if (l2)
temp->next = l2;
return q;
}
```
#### 新版
```cpp=36
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
list_ele_t *head = NULL;
list_ele_t **tmp = &head;
// sort and connect two list
// don't move head, use tmp traverse all list
while (l1 && l2) {
if (strnatcmp(l1->value, l2->value) < 0) {
*tmp = l1;
l1 = l1->next;
} else {
*tmp = l2;
l2 = l2->next;
}
tmp = &((*tmp)->next);
}
// connect remaining node
if (l1)
*tmp = l1;
if (l2)
*tmp = l2;
return head;
}
```
```cpp
list_ele_t *mergeSortList(list_ele_t *head)
{
// merge sort
if (!head || !head->next)
return head;
list_ele_t *fast = head->next;
list_ele_t *slow = head;
// split list, find the middle of list
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
// sort each list
list_ele_t *l1 = mergeSortList(head);
list_ele_t *l2 = mergeSortList(fast);
// merge sorted l1 and sorted l2
return merge(l1, l2);
}
```
一開始忘記 update q->tail 導致錯誤
```cpp
void q_sort(queue_t *q)
{
// NULL queue or element < 2
if (q == NULL || q->size < 2)
return;
q->head = mergeSortList(q->head);
// update q->tail pointer
list_ele_t *tmp = q->head;
while (tmp->next) {
tmp = tmp->next;
}
q->tail = tmp;
}
```
---
## Valgrind
- #### massif 實驗
寫 `q_free` 時,沒注意到 value 的釋放而導致錯誤,這邊試試看沒有釋放 value,明顯看出在最後的差別
```cmd
new
ih RAND 10000
sort
free
quit
```
- 有 free value
![](https://i.imgur.com/HhqRl6E.png)
- 沒有 free value
![](https://i.imgur.com/lWv6dDb.png)
---
## Dude, is my code constant time?
- [Dude, is my code constant time?](https://eprint.iacr.org/2016/1123.pdf)
- Measure execution time
- 分出 fix-vs -random 兩種不同 class
- 利用現代 CPU 的 cycle counters,可精準測量執行時間
- 為了降低環境影響,每次測量的 class 隨機,且會在測量之前將 input 做好
- post-processing
- Cropping: 排除因為 OS 或其他無關因素導致的極端值
- Higher-order preprocessing: 根據不同的 statistical test 做出不同調整
- `dudect` 相關
- `constant.c` 的 `measure`
分為 `test_size` 與 `test_insert_tail` 兩個 mode,測試是不是 O(1)
先在 `insert_head` 放入 `get_random_string` 得到的隨機 string,之後做 test 時會前後 call `cpucycles` 放入 `after_ticks` 跟 `before_ticks` ,之後相減就可以得到 cycle 數量
```c=60
if (mode == test_insert_tail) {
for (size_t i = drop_size; i < number_measurements - drop_size; i++)
char *s = get_random_string();
dut_new();
dut_insert_head(
get_random_string(),
*(uint16_t *) (input_data + i * chunk_size) % 10000);
before_ticks[i] = cpucycles();
dut_insert_tail(s, 1);
after_ticks[i] = cpucycles();
dut_free();
}
}
```
- `fixture.c`: 判斷是否在誤差範圍內
`test_tries` 代表整體測試的次數
在 10000 筆之中,使用 500 與 10 兩個 threshold,如果差距在 5% 以上則代表整個程式完全不符合 constant time
==未搞懂 threshold 如何設置==
```c=
#define enough_measurements 10000
#define test_tries 10
#define t_threshold_bananas 500
#define t_threshold_moderate 10
if (max_t > t_threshold_bananas) {
return false;
} else if (max_t > t_threshold_moderate) {
return false;
} else { /* max_t < t_threshold_moderate */
return true;
}
```
:::info
2021/02/24
在 cppcheck 2.3 dev 會遇到 unmatchedSuppression 的問題,
無法解讀 pre-commit.hook 內 suppresses 新增的 nullPointerRedundantCheck,
我將 cppcheck 的版本降為 1.9 後解決此問題。
> 提交 pull request 來改進 Cppcheck 相關操作?
> :notes: jserv
:::