# 2023q1 Homework1 (lab0) contributed by < [LiChiiiii](https://github.com/LiChiiiii/lab0-c) > ## 開發環境 ```shell $ gcc --version gcc (Ubuntu 11.3.0-1ubuntu1~22.04) 11.3.0 Copyright (C) 2021 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ lscpu Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz CPU family: 6 Model: 94 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Stepping: 3 CPU max MHz: 4000.0000 CPU min MHz: 800.0000 BogoMIPS: 6799.81 ``` ## 完成 queue.c :::success TOTAL SCORE : 100/100 ::: :::danger 注意書寫規範:中英文間用一個半形空白字元區隔。 :notes: jserv > 已修正 > [name=LiChiiiii] ::: #### `q_new` 利用 `malloc()` 配置記憶體空間,若記憶體配置失敗回傳 `NULL`,成功則使用 `list.h` 中的 `INIT_LIST_HEAD()` 來初始化 `head`。 ```c struct list_head *q_new() { struct list_head *head = malloc(sizeof(*head)); if (!head) return NULL; INIT_LIST_HEAD(head); return head; } ``` #### `q_free` 使用` list.h` 中的 `list_for_each_entry_safe()` 走訪佇列中每個節點,並使用 `q_release_element()` 釋放每一個元素,迴圈做完只會剩下指向佇列的 `l`,最後`free(l)`完成此功能。 :::info `list_for_each_entry_safe()` 會額外使用一個指標 `safe` 來暫存下一次循環的 entry,使得每次在執行 `q_release_element()` 時不會影響迴圈的執行。 ::: ```c void q_free(struct list_head *l) { if(!l) return; element_t *itm=NULL, *is=NULL; list_for_each_entry_safe (itm, is, l, list) { q_release_element(itm); } free(l); } ``` #### `q_insert_head` 先替 `new_itm` (欲新增至佇列的元素)配置一個資料結構為 `element_t` 的記憶體空間。 ```c typedef struct { char *value; struct list_head list; } element_t; ``` 計算 `s` (欲新增至 `new_itm->value` 的字元)大小,並依據計算出的大小配置記憶體空間至 `new_itm->value` ,透過條件式確認配置成功後,利用 `memcpy()` 將 `s` 複製到 `new_itm->value` 中,最後呼叫 `list_add()` 將 `new_itm` 元素加入佇列的第一個。 剛開始忘記在判斷配置失敗後要執行 `free(new_itm)` (第11行),導致報錯 `error: Memory leak: new_itm [memleak]` 。 ```c= bool q_insert_head(struct list_head *head, char *s) { if (!head) return false; element_t *new_itm = malloc(sizeof(*new_itm)); if (!new_itm) return false; size_t size = strlen(s) + 1; new_itm->value = malloc(size * sizeof(char)); if (!new_itm->value) { free(new_itm); return false; } memcpy(new_itm->value, s, size); list_add(&new_itm->list, head); return true; } ``` #### `q_insert_tail` 與 **`q_insert_head`** 的思路一樣,將第15行改成 `list_add_tail()` 。 ```c= bool q_insert_tail(struct list_head *head, char *s) { if (!head) return false; element_t *new_itm = malloc(sizeof(*new_itm)); if (!new_itm) return false; size_t size = strlen(s) + 1; new_itm->value = malloc(size * sizeof(char)); if (!new_itm->value) { free(new_itm); return false; } memcpy(new_itm->value, s, size); list_add_tail(&new_itm->list, head); return true; } ``` #### `q_remove_head` 使用 `list_first_entry()` 找到佇列中第一個元素,並透過 `list_del()` 刪除此元素。 使用 `strncpy()` 來複製字串至 `sp` 以避免 buffer overflow 的問題,但會產生以下狀況,若來源字元數小於限制複製字元的個數,剩下未填滿的部分會全部補上 '\0',反之則要手動補 '\0'(第9行)。 ```c= element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize) { if (!head) return NULL; element_t *itm = list_first_entry(head, element_t, list); list_del(&itm->list); if (sp != NULL) { strncpy(sp, itm->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return itm; } ``` #### `q_remove_tail` 與 **`q_remove_head`** 的思路一樣,將第5行改成 `list_last_entry()` 。 ```c= element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize) { if (!head) return NULL; element_t *itm = list_last_entry(head, element_t, list); list_del(&itm->list); if (sp != NULL) { strncpy(sp, itm->value, bufsize - 1); sp[bufsize - 1] = '\0'; } return itm; } ``` #### `q_size` 使用 `list.h` 中的 `list_for_each()` 走訪佇列,紀錄佇列長度。 ```c int q_size(struct list_head *head) { int size = 0; if (!head) return size; struct list_head *itm; list_for_each (itm, head) { size++; } return size; } ``` #### `q_delete_mid` 利用 doubly-linked list 的特性,宣告兩個指標 `front` 和 `rear` 分別**從前向後**和**從後到前**同時移動,直到重疊即可找到 `mid` 並刪除。利用 `front!=rear->prev` 條件式來尋找含偶數個元素的佇列之 `mid` ,利用 `front!=rear` 條件式來尋找含奇數個元素的佇列之 `mid` (第6行)。 原本的寫法是先寫 `q_release_element(mid)` ,才接著寫 `list_del(front)` ,這樣會先把 `front` 指向的元素之記憶體空間釋放掉,導致報錯: `Segmentation fault occurred. You dereferenced a NULL or invalid pointerAborted (core dumped)` ```c= bool q_delete_mid(struct list_head *head) { if (!head) return false; struct list_head *front = head->next, *rear = head->prev; while (front != rear->prev && front != rear) { front = front->next; rear = rear->prev; } element_t *mid = list_entry(front, element_t, list); list_del(front); q_release_element(mid); return true; } // https://leetcode.com/problems/delete-the-middle-node-of-a-linked-list/ ``` #### `q_delete_dup` `flag` :決定當前元素是否為應被刪除的元素 利用迴圈比較當前元素 `cur_el` 以及前一個元素 `cur_prev_el` 之值是否相同,若相同則刪除 `cur_prev_el` 並設定 `flag=1` ,若不相同但 `flag=1` 則代表當前元素 `cur_el` 為重複元素中最後一個元素,也就是應被刪除的元素。 ```c bool q_delete_dup(struct list_head *head) { if (!head || list_is_singular(head) || list_empty(head)) return false; int flag = 0; struct list_head *cur, *safe = NULL; list_for_each_safe (cur, safe, head) { if (cur->prev == head) continue; element_t *cur_el = list_entry(cur, element_t, list); element_t *cur_prev_el = list_entry(cur->prev, element_t, list); if (!strcmp(cur_el->value, cur_prev_el->value)) { list_del(cur->prev); q_release_element(cur_prev_el); flag = 1; if (cur == head->prev){ list_del(cur); q_release_element(cur_el); return true; } } else if (flag == 1) { list_del(cur->prev); q_release_element(cur_prev_el); flag = 0; } } return true; } // https://leetcode.com/problems/remove-duplicates-from-sorted-list-ii/ ``` #### `q_swap` `flag` :決定當前元素是否與前一個元素交換 若 `flag=0` 則設定 `flag=1`並跳過此元素,若 `flag=1` 則當前元素與前一個元素交換(前一個元素移到當前元素後面)並設定 `flag=0` 。 ```c void q_swap(struct list_head *head) { if (!head || list_is_singular(head) || list_empty(head)) return; int flag = 0; struct list_head *cur, *safe = NULL; list_for_each_safe (cur, safe, head) { if (cur->prev == head || flag == 0) { flag = 1; continue; } else if (flag == 1) { list_move(cur->prev, cur); flag = 0; } } return; } // https://leetcode.com/problems/swap-nodes-in-pairs/ ``` #### `q_reverse` `list_move()` 會先執行 `list_del()` 將當前指向的元素刪除,在執行 `list_add()` 將元素新增至佇列最前端。第 7 行使用 `list_for_each_safe()` 可以允許當前節點從佇列中移除,用 `list_for_each()` 會導致 infinite loop 。 ```c= void q_reverse(struct list_head *head) { if(!head || list_is_singular(head) || list_empty(head)) return; struct list_head *cur , *safe=NULL; list_for_each_safe(cur, safe, head){ list_move(cur, head); } } ``` #### `q_reverseK` 讓指標邊走邊數 k 個元素,到第 k 個元素進行 reverse 。 `cur_prev_k` 存放 `top` 為 1 的元素之記憶體位址,也就是記錄當前元素要進行 reverse時的前 k 個元素之記憶體位址。 :::info 宣告改成 `*cur_prev_k = head->next` 會出現無限迴圈,因為 `cur_prev_k` 會跟著指向的元素做移動,導致 `cur_prev_k` 永遠不會等於 `tmp` ,也就是離不開 while 迴圈。 ::: ```c void q_reverseK(struct list_head *head, int k) { if(!head || list_is_singular(head) || list_empty(head)) return; int top = 0; struct list_head *cur, **cur_prev_k = &(head->next), struct list_head *safe=NULL, *tmp=NULL; list_for_each_safe(cur, safe, head){ top++; if(top%k==0){ tmp = cur; while(*cur_prev_k != tmp){ list_move(tmp->prev, cur); cur = cur->next; } cur_prev_k = &(cur->next); } } // https://leetcode.com/problems/reverse-nodes-in-k-group/ } ``` #### `q_sort` 參考[你所不知道的 c 語言: linked list 和非連續記憶體](https://hackmd.io/@sysprog/c-linked-list#Merge-Sort-%E7%9A%84%E5%AF%A6%E4%BD%9C)、 [Linked List Sort](https://npes87184.github.io/2015-09-12-linkedListSort/) 中提到的 Merge Sort 以及 [seasonwang0905](https://hackmd.io/@seasonwang/B1B0lVqpo) 的程式碼實作。 :::danger 避免張貼完整程式碼,列出關鍵程式碼並闡述你的觀察、推論,和考慮議題。 :notes: jserv > 已修正 > [name=LiChiiiii] ::: 排序實作總共用到三個函式: 1. `merge_two_list()` :將兩個佇列合併成一個已完成排序的佇列 2. `mergesort()`:將未排序的佇列進行不斷的分割,直到剩一個節點即開始執行`merge_two_list()`,直到遞迴結束。 3. `q_sort()`:先將尚未排序的佇列變成 singly-linked list ,這樣排序過程中就不用維持 doubly-linked list 。等到執行完 `mergesort()` 排序完畢後,再重新連結 prev 即可。 ```c /* Merge two list into one queue */ struct list_head *merge_two_list(struct list_head *l1, struct list_head *l2) { struct list_head *temp = NULL; struct list_head **indirect = &temp; for (struct list_head **node = NULL; l1 && l2; *node = (*node)->next) { element_t *e1 = list_entry(l1, element_t, list); element_t *e2 = list_entry(l2, element_t, list); if (strcmp(e1->value, e2->value) < 0) node = &l1; else node = &l2; *indirect = *node; indirect = &(*indirect)->next; } *indirect = (struct list_head *) ((u_int64_t) l1 | (u_int64_t) l2); return temp; } /* Divide the queue and sort every element */ struct list_head *mergesort(struct list_head *head) { if (!head || !head->next) return head; struct list_head *fast = head, *slow = head; while (fast && fast->next) { fast = fast->next->next; slow = slow->next; } fast = slow; slow->prev->next = NULL; struct list_head *l1 = mergesort(head); struct list_head *l2 = mergesort(fast); return merge_two_list(l1, l2); } /* Sort elements of queue in ascending order */ void q_sort(struct list_head *head) { if (!head || list_empty(head)) return; head->prev->next = NULL; head->next = mergesort(head->next); struct list_head *current = head, *next = head->next; while (next) { next->prev = current; current = next; next = next->next; } current->next = head; head->prev = current; } ``` #### `q_descend` 從 list 尾端向前比較大小,比從 list 前端向後比較大小來的簡單。前者只要反向比較下一個元素小於當前元素即可刪除,後者則是順向比較當前元素之前的所有元素是否小於當前元素。 ```c int q_descend(struct list_head *head) { if (!head || list_is_singular(head) || list_empty(head)) return 0; struct list_head *cur = head->prev; while (cur->prev != head) { element_t *cur_el = list_entry(cur, element_t, list); element_t *cur_prev_el = list_entry(cur->prev, element_t, list); int cmp = strcmp(cur_el->value, cur_prev_el->value); if (cmp > 0) { list_del(cur->prev); q_release_element(cur_prev_el); } else { cur = cur->prev; } } return q_size(head); } // https://leetcode.com/problems/remove-nodes-from-linked-list/ ``` #### `q_merge` 使用 `queue.h` 中定義的資料結構 `queue_contex_t` 來表示每一個queue。 ```c typedef struct { struct list_head *q; struct list_head chain; int size; int id; } queue_contex_t; ``` 透過 `list_for_each_entry()` 查看所有的佇列,並將佇列合併,最後將合併起來的queue做排序。 ```c /* Merge all the queues into one sorted queue, which is in ascending order */ int q_merge(struct list_head *head) { if (!head || list_empty(head)) return 0; else if(list_is_singular(head)) return list_entry(head, queue_contex_t,chain)->size; queue_contex_t *que = list_entry(head->next, queue_contex_t,chain); queue_contex_t *cur_que = NULL; list_for_each_entry(cur_que, head, chain) { if(cur_que == que) continue; list_splice_init(cur_que->q, que->q); que->size = que->size + cur_que->size; cur_que->size = 0; } q_sort(que->q); return que->size; // https://leetcode.com/problems/merge-k-sorted-lists/ } ``` ## 改進 `web` 命令 原始程式碼執行 `web` 命令可開啟網頁伺服器,一旦 web_fd > 0 表示成功開啟 web server 。 ```c /* console.c */ static int web_fd; static bool do_web(int argc, char *argv[]) { int port = 9999; if (argc == 2) { if (argv[1][0] >= '0' && argv[1][0] <= '9') port = atoi(argv[1]); } web_fd = web_open(port); if (web_fd > 0) { printf("listen on port %d, fd is %d\n", port, web_fd); use_linenoise = false; } else { perror("ERROR"); exit(web_fd); } return true; } ``` 在 `web.c` 可以看到已經寫好在 web 接收訊息和傳送訊息的處理方式。 ```c /* web.c */ char *web_recv(int fd, struct sockaddr_in *clientaddr) { http_request_t req; parse_request(fd, &req); char *p = req.filename; /* Change '/' to ' ' */ while (*p) { ++p; if (*p == '/') *p = ' '; } char *ret = malloc(strlen(req.filename) + 1); strncpy(ret, req.filename, strlen(req.filename) + 1); return ret; } void web_send(int out_fd, char *buf) { writen(out_fd, buf, strlen(buf)); } ``` 問題在於網頁伺服器開啟後,處理命令列的 `linenoise` 套件無法使用,因此嘗試改寫 `linenoise.c` 用 [select()](https://man7.org/linux/man-pages/man2/select.2.html) 同時處理 stdin 及 socket。 在`line_edit` 新增參數 `web_fd` ,當 web_fd != 0 就使用 `select()` 監聽。若從 web 輸入訊息(第33行),則將訊息複製到 buf 中並透過 linenoise 處理命令,同時也傳送 HTTP 回應的 header 訊息給客戶端,表示回應成功,並關閉 socket 。若從命令列輸入訊息(第43行),則保持原本做法處理。 ```c= /* linenoise.c */ static int line_edit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt, int web_fd) { ... while (1) { signed char c; int nread; char seq[5]; if (web_fd) { fd_set set; FD_ZERO(&set); FD_SET(web_fd, &set); FD_SET(stdin_fd, &set); int rv = select(web_fd + 1, &set, NULL, NULL, NULL); struct sockaddr_in clientaddr; socklen_t clientlen = sizeof(clientaddr); int connfd; switch (rv) { case -1: perror("select"); /* an error occurred */ continue; case 0: printf("timeout occurred\n"); /* a timeout occurred */ continue; default: if (FD_ISSET(web_fd, &set)) { FD_CLR(web_fd, &set); connfd = accept(web_fd, (struct sockaddr *) &clientaddr, &clientlen); strncpy(buf, web_recv(connfd, &clientaddr), buflen); char *header = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\n"; web_send(web_connfd, header); close(web_connfd); return strlen(buf); } else if (FD_ISSET(stdin_fd, &set)) { nread = read(l.ifd, &c, 1); if (nread <= 0) return l.len; } break; } } else { nread = read(l.ifd, &c, 1); if (nread <= 0) return l.len; } } ... } ``` > 參考老師的[整合網頁伺服器](https://hackmd.io/@sysprog/linux2023-lab0/%2F%40sysprog%2Flinux2023-lab0-c) ## 實作 `shuffle` 命令 利用 Fisher–Yates shuffle 演算法來實作洗牌。 ```c /* queue.c */ void value_swap(struct list_head *node1, struct list_head *node2) { element_t *node1_ele = list_entry(node1, element_t, list); element_t *node2_ele = list_entry(node2, element_t, list); char *temp; temp = node1_ele->value; node1_ele->value = node2_ele->value; node2_ele->value = temp; } void q_shuffle(struct list_head *head) { if (!head || list_is_singular(head) || list_empty(head)) return; struct list_head *last = head->prev; struct list_head *cur = head->next; int len = q_size(head); srand(time(NULL)); while(len) { int i = rand() % len; while(i) { cur = cur->next; i--; } value_swap(cur,last); last = last->prev; cur = head->next; len--; } } ``` 原本 swap 的寫法是透過 `list_move()` 來移動兩個節點位置,但執行 shuffle 時偶爾會失敗,後來才改成交換 value 的方法。 ```c void node_swap(struct list_head *node1, struct list_head *node2) { struct list_head *node1_prev = node1->prev; struct list_head *node2_prev = node2->prev; list_move(node2, node1_prev); list_move(node1, node2_prev); } ``` 除了實作程式碼,還需要在 `qtest.c` 新增命令和執行函式。 ```c ADD_COMMAND(shuffle, "Implement Fisher–Yates shuffle",""); ``` ```c static inline bool do_shuffle(int argc, char *argv[]) { if (argc != 1) { report(1, "%s takes no arguments", argv[0]); return false; } if (!current) { report(3, "Warning: Try to operate null queue"); return false; } error_check(); set_noallocate_mode(true); if (current && exception_setup(true)) q_shuffle(current->q); exception_cancel(); set_noallocate_mode(false); return q_show(3); } ``` ## Address Sanitizer 在 `Makefile` 中發現已定義一個變數稱 `SANITIZER` ,當 `SANITIZER` 變數設置為 1,即可啟用 address sanitizer。 ``` # Enable sanitizer(s) or not ifeq ("$(SANITIZER)","1") # https://github.com/google/sanitizers/wiki/AddressSanitizerFlags CFLAGS += -fsanitize=address -fno-omit-frame-pointer -fno-common LDFLAGS += -fsanitize=address endif ``` 執行下列命令開啟 address sanitizer ,並重新編譯,沒有出現任何關於記憶體之錯誤。 ```shell make clean make SANITIZER=1 make test ``` :::spoiler 執行結果 ```shell $ make test scripts/driver.py -c --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 ``` ::: ## Valgrind 執行 `make valgrind` 沒有出現相關問題 :::spoiler 執行結果 ``` $ make valgrind # Explicitly disable sanitizer(s) make clean SANITIZER=0 qtest make[1]: Entering directory '/home/lichi/Documents/lab0-c' rm -f qtest.o report.o console.o harness.o queue.o random.o dudect/constant.o dudect/fixture.o dudect/ttest.o shannon_entropy.o linenoise.o web.o .qtest.o.d .report.o.d .console.o.d .harness.o.d .queue.o.d .random.o.d .dudect/constant.o.d .dudect/fixture.o.d .dudect/ttest.o.d .shannon_entropy.o.d .linenoise.o.d .web.o.d *~ qtest /tmp/qtest.* rm -rf .dudect rm -rf *.dSYM (cd traces; rm -f *~) CC qtest.o CC report.o CC console.o CC harness.o CC queue.o CC random.o CC dudect/constant.o CC dudect/fixture.o CC dudect/ttest.o CC shannon_entropy.o CC linenoise.o CC web.o LD qtest make[1]: Leaving directory '/home/lichi/Documents/lab0-c' cp qtest /tmp/qtest.J1q9iz chmod u+x /tmp/qtest.J1q9iz sed -i "s/alarm/isnan/g" /tmp/qtest.J1q9iz scripts/driver.py -p /tmp/qtest.J1q9iz --valgrind --- Trace Points +++ TESTING trace trace-01-ops: # Test of insert_head and remove_head --- trace-01-ops 5/5 +++ TESTING trace trace-02-ops: # Test of insert_head, insert_tail, remove_head, remove_tail, and delete_mid --- trace-02-ops 6/6 +++ TESTING trace trace-03-ops: # Test of insert_head, insert_tail, remove_head, reverse and merge --- trace-03-ops 6/6 +++ TESTING trace trace-04-ops: # Test of insert_head, insert_tail, size, swap, and sort --- trace-04-ops 6/6 +++ TESTING trace trace-05-ops: # Test of insert_head, insert_tail, remove_head, reverse, size, swap, and sort --- trace-05-ops 6/6 +++ TESTING trace trace-06-ops: # Test of insert_head, insert_tail, delete duplicate, sort, descend and reverseK --- trace-06-ops 6/6 +++ TESTING trace trace-07-string: # Test of truncated strings --- trace-07-string 6/6 +++ TESTING trace trace-08-robust: # Test operations on empty queue --- trace-08-robust 6/6 +++ TESTING trace trace-09-robust: # Test remove_head with NULL argument --- trace-09-robust 6/6 +++ TESTING trace trace-10-robust: # Test operations on NULL queue --- trace-10-robust 6/6 +++ TESTING trace trace-11-malloc: # Test of malloc failure on insert_head --- trace-11-malloc 6/6 +++ TESTING trace trace-12-malloc: # Test of malloc failure on insert_tail --- trace-12-malloc 6/6 +++ TESTING trace trace-13-malloc: # Test of malloc failure on new --- trace-13-malloc 6/6 +++ TESTING trace trace-14-perf: # Test performance of insert_tail, reverse, and sort --- trace-14-perf 6/6 +++ TESTING trace trace-15-perf: # Test performance of sort with random and descending orders # 10000: all correct sorting algorithms are expected pass # 50000: sorting algorithms with O(n^2) time complexity are expected failed # 100000: sorting algorithms with O(nlogn) time complexity are expected pass --- trace-15-perf 6/6 +++ TESTING trace trace-16-perf: # Test performance of insert_tail --- trace-16-perf 6/6 +++ TESTING trace trace-17-complexity: # Test if time complexity of q_insert_tail, q_insert_head, q_remove_tail, and q_remove_head is constant Probably constant time Probably constant time Probably constant time Probably constant time --- trace-17-complexity 5/5 --- TOTAL 100/100 ``` ::: ### 使用 Massif 查看記憶體的狀況 ``` valgrind --tool=massif ./qtest -f traces/trace-17-complexity.cmd ``` 以下是 `traces/trace-17-complexity.cmd` 的記憶體使用狀況 ![](https://i.imgur.com/yr2Y1wi.jpg) 在 trace-17 執行了 `q_insert_head()` 、 `q_insert_tail()` 、 `q_remove_head()` 以及 `q_remove_tail()` 四個函式,在圖中的最右邊可以看到,執行結束後記憶體並沒有釋放完全,此部分待確認。 ## Linux 核心原始程式碼的 `lib/list_sort.c ` #### 比較 `lib/list_sort.c` 與自行實作的合併排序演算法差異 由於無法新增 `queue.h` 的定義,所以就把 `lib_sort.c` 的程式放進 `qtest.c` ,新增一個 option 來切換 sort 的實作,這樣很快就可以看看自己實作的效能跟 Linux 核心程式碼的差距。 在 `qtest.c` 當中把 `do_sort()` 函式的部分改為這樣: ```c if (exception_setup(true)) { if (is_enable_linux_sort()) { q_sort_linux(l_meta.l); } else { q_sort(l_meta.l); } } ``` 參考 [komark06](https://hackmd.io/@komark06/SyCUIEYpj#引入-liblist_sortc) 對 `list_sort.c` 的修改方法,將程式放入 `qtest.c` 執行。 接著利用以下的 command 來比較排序時間: ``` option fail 0 option malloc 0 new ih dolphin 1000000 it gerbil 1000000 reverseK 100 time sort option linux_qsort 1 new ih dolphin 1000000 it gerbil 1000000 reverseK 100 time sort ``` :::warning 執行 `list_sort.c` 會出現 `Segmentation fault occurred.` ,應是引入程式碼時有錯誤,待處理。 ::: ## 研讀論文〈Dude, is my code constant time?〉 論文中提出了工具 **dudect** ,可以用來檢測一段程式是否 constant time 執行。 測試方式主要分成三個步驟: 1. Measure execution time : 輸入**固定資料**和**隨機資料**來量測目標函式耗費的時間。每次隨機選擇一種類型資料並重複執行得到量測結果。 2. Apply post-processing:大於某個 threshold 的執行時間不列入統計檢定。 3. Apply statistical test: 論文中提及兩個檢驗方式 `t-test` (採用 [Welch's t-test](https://en.wikipedia.org/wiki/Welch%27s_t-test) ) 和 `Non-parametric tests` :::info :stars: **t-test** t-test 是一個常用來協助判斷**比較兩組資料是否具有顯著差異**的統計方法之一。在這裡我們試圖**反證兩組資料時間分佈相等**,也就是說當 t-test 拒絕假說(無法證明平均值的差異),表示執行時間為 constant time。 ::: ### 程式實作原理 在 lab0-c 中,當 `simulation` 開啟後會將 `is_xx_xx_const()` 作為目標函式進行時間複雜度測試 `test_const()` $\rightarrow$ `doit()` ,並根據論文中的三個步驟實作: 1. `measure()` 和 `differentiate()`:計算目標函式執行時間 2. `update_statistics()`:處理取得的資料 3. `report()`:利用 [Welch's t-test](https://en.wikipedia.org/wiki/Welch%27s_t-test) 驗證目標函式是否為 constant time ```c /* dudect/fixture.c */ static bool doit(int mode) { ... bool ret = measure(before_ticks, after_ticks, input_data, mode); differentiate(exec_times, before_ticks, after_ticks); update_statistics(exec_times, classes); ret &= report(); ... return ret; } ``` 在 `measure()` 函式發現計算過程中**直接減掉要做刪除的次數**(第10行),但測試流程應該要是**紀錄所有資料再去做第二步驟的資料處理**。 ```c /* dudect/constant.h */ #define N_MEASURES 150 #define DROP_SIZE 20 ``` ```c= /* dudect/constant.c */ bool measure(int64_t *before_ticks, int64_t *after_ticks, uint8_t *input_data, int mode) { ... switch (mode) { case DUT(insert_head): for (size_t i = DROP_SIZE; i < N_MEASURES - DROP_SIZE; i++) { char *s = get_random_string(); dut_new(); dut_insert_head( get_random_string(), *(uint16_t *) (input_data + i * CHUNK_SIZE) % 10000); int before_size = q_size(l); before_ticks[i] = cpucycles(); dut_insert_head(s, 1); after_ticks[i] = cpucycles(); int after_size = q_size(l); dut_free(); if (before_size != after_size - 1) return false; } break; ... ``` 因此將該部分改成做完 `N_MEASURES` 次的紀錄後進行排序再刪除前後各 `DROP_SIZE` 個資料點,即可每次皆通過 `trace-17-complexity` 的測試。 > 參考 [KHLee529](https://hackmd.io/@KHLee529/linux2023-lab0 ) 解決方法 ## Debug 紀錄 * 編譯時出現以下錯誤訊息 ```! gcc: internal compiler error: Segmentation fault signal terminated program as Please submit a full bug report, with preprocessed source if appropriate. See <file:///usr/share/doc/gcc-11/README.Bugs> for instructions. make: *** [Makefile:54: qtest.o] Error 4 ``` > 1. 檢查gcc版本 > 2. 檢查可用的記憶體空間 > 3. 重新啟動主機