owned this note
owned this note
Published
Linked with GitHub
# 2020q3 Homework1 (lab0)
contributed by < `quantabase13` >
說明
- [作業說明](https://hackmd.io/@sysprog/2020-lab0)
## 作業要求
實作 queue,此 queue 能從 head 處插入 element。
- 需要實現的函式:
- `q_new` - 新增空的 queue
- `q_free` - 清除整個 queue
- `q_insert_head` - 在 queue 的 head 前插入元素
- `q_insert_tail` - 在 queue 的 tail 後插入元素
- `q_remove_head` - 取出並移除 queue 的第一個元素
- `q_size`- 給出 queue 中的元素個數
- `q_reverse`- 反轉整個 queue ,並且只使用原本 queue 中的 element
- `q_sort` - 對 queue 進行遞增排序(時間複雜度:O(nlog(n))
## 實作流程
### queue.h
* 修改 `queue_t`,新增 `size`和 `tail` 這兩個 member
* 從程式碼中的註解可得知 `q_size` 和 `q_insert_tail` 的時間複雜度限制為 O(1)。透過這種方式可以讓 `q_size` 直接返回 `size` 的值 ; 讓 `q_insert_tail` 不需計算 tail 的位置。
```c=
/* Queue structure */
typedef struct {
list_ele_t *head; /* Linked list of elements */
/* TODO: You will need to add more fields to this structure
* to efficiently implement q_size and q_insert_tail.
*/
/* TODO: Remove the above comment when you are about to implement. */
list_ele_t *tail;
int size;
} queue_t;
```
### queue.c
* 要點(不含`q_sort`部份)
* 注意 `malloc` 是否有成功分配記憶體
* 注意不再被使用的記憶體要記得 `free`
* 要點( `q_sort` 實作相關)
* merge sort 的實現方式(遞歸或迭代)
* ## `q_size`
```c=
/*
* Return number of elements in queue.
* Return 0 if q is NULL or empty
*/
int q_size(queue_t *q)
{
/* TODO: You need to write the code for this function */
/* Remember: It should operate in O(1) time */
/* TODO: Remove the above comment when you are about to implement. */
if (!q)
return 0;
return q->size;
}
```
`q_size` 確認傳入的 pointer 不為 NULL 後才對其取值。同時透過為 queue_t 加入 size 後可讓所需時間為 O(1)。
* ## q_new
```c=
/*
* Create empty queue.
* Return NULL if could not allocate space.
*/
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
/* TODO: What if malloc returned NULL? */
if (q) {
q->head = NULL;
q->tail = NULL;
q->size = 0;
}
return q;
}
```
一樣根據程式碼中的註解,在實作中加入對記憶體分配失敗時的處理。
* ## q_free
```c=
/* Free all storage used by queue */
void q_free(queue_t *q)
{
/* TODO: How about freeing the list elements and the strings? */
/* Free queue structure */
list_ele_t *next;
if (!q)
return;
for (list_ele_t *indirect = q->head; indirect != NULL;) {
free(indirect->value);
next = (indirect)->next;
free(indirect);
indirect = next;
}
free(q);
}
```
利用 `next` 和 `indirect` 兩個 pointer 遍歷整個 queue 來釋放記憶體。釋放記憶體的順序為:
1. 元素結構中的 `*char` pointer 指向的記憶體
2. 元素結構佔有的記憶體
3. queue 結構佔有的記憶體
* ## q_insert_head
```c=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
char *newstr;
int len = strlen(s);
/* TODO: What should you do if the q is NULL? */
if (!q) {
return false;
}
newh = malloc(sizeof(list_ele_t));
if (!newh) {
return false;
}
newstr = malloc(sizeof(char) * len + 1);
if (!newstr) {
free(newh);
return false;
}
for (int i = 0; i < len; i++) {
*(newstr + i) = *(s + i);
}
*(newstr + len) = '\0';
/* Don't forget to allocate space for the string and copy it */
/* What if either call to malloc returns NULL? */
newh->next = q->head;
newh->value = newstr;
q->head = newh;
if (!(q->tail))
q->tail = newh;
(q->size)++;
return true;
}
```
根據傳進來 string 的長度,動態配置記憶體這裡必須要注意 `malloc` 的結果是否成功。為了突顯記憶體分配的問題,作業中的 `malloc` 其實已經被包裝過,在 `harness.h` 檔案中可以看到如下程式碼:
```c=
/* Tested program use our versions of malloc and free */
#define malloc test_malloc
#define free test_free
```
可以得知 `malloc` 在作業裡被替換成 `test_malloc` 。追蹤到 `harness.c` 檔後,可以看到 `test_malloc` 的具體實現:
```c=
/*
* Implementation of application functions
*/
void *test_malloc(size_t size)
{
if (noallocate_mode) {
report_event(MSG_FATAL, "Calls to malloc disallowed");
return NULL;
}
if (fail_allocation()) {
report_event(MSG_WARN, "Malloc returning NULL");
return NULL;
}
block_ele_t *new_block =
malloc(size + sizeof(block_ele_t) + sizeof(size_t));
if (!new_block) {
report_event(MSG_FATAL, "Couldn't allocate any more memory");
error_occurred = true;
}
// cppcheck-suppress nullPointerRedundantCheck
new_block->magic_header = MAGICHEADER;
// cppcheck-suppress nullPointerRedundantCheck
new_block->payload_size = size;
*find_footer(new_block) = MAGICFOOTER;
void *p = (void *) &new_block->payload;
memset(p, FILLCHAR, size);
// cppcheck-suppress nullPointerRedundantCheck
new_block->next = allocated;
// cppcheck-suppress nullPointerRedundantCheck
new_block->prev = NULL;
if (allocated)
allocated->prev = new_block;
allocated = new_block;
allocated_count++;
return p;
}
```
可以看到,在呼叫真正 `malloc` 之前,多了一個 `fail_allocation` 函式。
在 `harness.c` 檔裡同樣可看到 `fail_allocation` 的實現:
```c=
/* Should this allocation fail? */
static bool fail_allocation()
{
double weight = (double) random() / RAND_MAX;
return (weight < 0.01 * fail_probability);
}
```
我們從程式碼中可以得知, `fail_allocation` 代表分配記憶體失敗的機率。在 `harness.c` 檔裡可以看到, `fail_probability` 的值預設為0 。
在作業程式碼中搜尋 `fail_probaility` 時在 `q_test.c` 有找到以下程式碼:
```c=
static void console_init()
{
add_cmd("new", do_new, " | Create new queue");
add_cmd("free", do_free, " | Delete queue");
add_cmd("ih", do_insert_head,
" str [n] | Insert string str at head of queue n times. "
"Generate random string(s) if str equals RAND. (default: n == 1)");
add_cmd("it", do_insert_tail,
" str [n] | Insert string str at tail of queue n times. "
"Generate random string(s) if str equals RAND. (default: n == 1)");
add_cmd("rh", do_remove_head,
" [str] | Remove from head of queue. Optionally compare "
"to expected value str");
add_cmd(
"rhq", do_remove_head_quiet,
" | Remove from head of queue without reporting value.");
add_cmd("reverse", do_reverse, " | Reverse queue");
add_cmd("sort", do_sort, " | Sort queue in ascending order");
add_cmd("size", do_size,
" [n] | Compute queue size n times (default: n == 1)");
add_cmd("show", do_show, " | Show queue contents");
add_param("length", &string_length, "Maximum length of displayed string",
NULL);
add_param("malloc", &fail_probability, "Malloc failure probability percent",
NULL);
add_param("fail", &fail_limit,
"Number of times allow queue operations to return false", NULL);
}
```
再搭配用來測試的腳本,以 `trace-10-malloc.cmd` 的內容為例:
```=
# Test of malloc failure on new
option fail 10
option malloc 50
new
new
new
new
new
new
```
從這些程式碼,我們得知程式執行期間我們可以輸入的命令及參數。以 `option malloc 50` 為例,透過這個命令,能將 `fail_probability` 從原本的 `0` 修改為 `50` 。
* ## q_insert_tail
```c=
bool q_insert_tail(queue_t *q, char *s)
{
/* TODO: You need to write the complete code for this function */
/* Remember: It should operate in O(1) time */
/* TODO: Remove the above comment when you are about to implement. */
list_ele_t *newt;
char *newstr;
int len = strlen(s);
if (!q)
return false;
newt = malloc(sizeof(list_ele_t));
if (!newt) {
return false;
}
newstr = malloc(sizeof(char) * len + 1);
if (!newstr) {
free(newt);
return false;
}
for (int i = 0; i < len; i++) {
*(newstr + i) = *(s + i);
}
*(newstr + len) = '\0';
newt->next = NULL;
newt->value = newstr;
if (!q->tail) {
q->head = newt;
q->tail = newt;
} else {
q->tail->next = newt;
q->tail = newt;
}
(q->size)++;
return true;
}
```
`q_insert_tail` 的實作與 `q_insert_head` 基本相同。
* ## q_remove_head
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
/* TODO: You need to fix up this code. */
/* TODO: Remove the above comment when you are about to implement. */
list_ele_t *tmp;
int i;
if (!q)
return false;
if (q->size == 0)
return false;
if (!sp)
return true;
for (i = 0; (i < bufsize - 1) && (*(q->head->value + i) != '\0'); i++) {
*(sp + i) = *(q->head->value + i);
}
*(sp + i) = '\0';
tmp = q->head;
q->head = q->head->next;
q->size--;
free(tmp->value);
free(tmp);
return true;
}
```
這裡要注意的是 `bufsize` 代表可儲存的最大長度,而非一定要放滿 `bufsize` 的長度。因此在 `for` 迴圈中需要加入來源 `pointer` 指向的 `char` 是否為 `'\0'` 的檢查。
* ## q_reverse
```c=
void q_reverse(queue_t *q)
{
/* TODO: You need to write the code for this function */
/* TODO: Remove the above comment when you are about to implement. */
if (!q)
return;
if (q->size == 0)
return;
list_ele_t *cur = NULL;
list_ele_t *header = q->head;
while (header) {
list_ele_t *next = header->next;
header->next = cur;
cur = header;
header = next;
}
list_ele_t *tmp = q->head;
q->head = q->tail;
q->tail = tmp;
return;
}
```
`q_reverse` 的實現在第一周上課的小考就有出現,畫出各個 `pointer` 指向的變化就能一目了然。
* ## q_sort
`q_sort` 我實作了兩次,第一次是用全遞迴的方式:
```c=
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2);
list_ele_t *mergeSort(list_ele_t *head);
void q_sort(queue_t *q)
{
/* TODO: You need to write the code for this function */
/* TODO: Remove the above comment when you are about to implement. */
if (q == NULL || q->size == 0 || q->size == 1) {
return;
}
q->head = mergeSort(q->head);
while (q->tail->next) {
q->tail = q->tail->next;
}
}
list_ele_t *merge(list_ele_t *l1, list_ele_t *l2)
{
if (!l2)
return l1;
if (!l1)
return l2;
if (strcmp(l1->value, l2->value) < 0) {
l1->next = merge(l1->next, l2);
return l1;
} else {
l2->next = merge(l2->next, l1);
return l2;
}
}
list_ele_t *mergeSort(list_ele_t *head)
{
if (!head || !head->next)
return head;
list_ele_t *fast = head->next;
list_ele_t *slow = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
fast = slow->next;
slow->next = NULL;
list_ele_t *l1 = mergeSort(head);
list_ele_t *l2 = mergeSort(fast);
return merge(l1, l2);
}
```
全遞迴的版本無法通過 trace-15 ,會出現 `Segmentation fault (core dumped)`的錯誤訊息。
確認過程式碼不會 dereference `NULL pointer` 後,推測是遞迴用到的 stack 空間過多導致。
於是第二次參考[ZhuMon](https://hackmd.io/@ZhuMon/lab0-c#2020q1-Homework1-lab0),將 `merge` 的部份以迭代實現:
```c=
void mergeSort(list_ele_t **head);
void q_sort(queue_t *q)
{
/* TODO: You need to write the code for this function */
/* TODO: Remove the above comment when you are about to implement. */
if (q == NULL || q->size == 0 || q->size == 1) {
return;
}
mergeSort(&q->head);
while (q->tail->next) {
q->tail = q->tail->next;
}
}
void mergeSort(list_ele_t **head)
{
if (!*head || !(*head)->next)
return;
list_ele_t *l1 = (*head)->next;
list_ele_t *l2 = (*head);
while (l1 && l1->next) {
l2 = l2->next;
l1 = l1->next->next;
}
l1 = l2->next;
l2->next = NULL;
l2 = *head;
mergeSort(&l1);
mergeSort(&l2);
*head = NULL;
list_ele_t **cur = head;
while (l1 && l2) {
if (strcmp(l1->value, l2->value) < 0) {
*cur = l1;
l1 = l1->next;
} else {
*cur = l2;
l2 = l2->next;
}
cur = &(*cur)->next;
}
*cur = l1 ? l1 : l2;
}
```
程式碼利用 `l1` , `l2` 指向等待被 merge 的 linked list 的第一個元素 , 利用 `cur` 這個 pointer to pointer 訪問並修改原本 linked list 元素的 next 欄位
```graphviz
digraph linked_list{
rankdir=LR
node [shape=record];
1 [label="{ <data> 1 | <ref> }"];
3 [label="{ <data> 3 | <ref> }"];
5 [label="{ <data> 5 | <ref> }"];
7 [label="{ <data> 7 | <ref> }"];
cur[label="{ <data> cur }"];
cur -> "1":ref[tailclip=false]
l1 -> 3 [ tailclip=false]
1:ref:c -> 3:data [arrowtail=dot, dir=both, tailclip=false];
3:ref:c -> 5:data [arrowtail=dot, dir=both, tailclip=false];
5:ref:c -> 7:data [arrowtail=dot, dir=both, tailclip=false];
7:ref:c ->NULL [arrowtail=dot,
dir=both, tailclip=false]
2 [label="{ <data> 2| <ref> }"];
4 [label="{ <data> 4 | <ref> }"];
6 [label="{ <data> 6 | <ref> }"];
8 [label="{ <data> 8 | <ref> }"];
l1 [label="{ <data> l1 }"];
l2 [label="{ <data> l2 }"];
2:ref:f -> 4:data [arrowtail=dot, dir=both, tailclip=false];
4:ref:g -> 6:data [arrowtail=dot, dir=both, tailclip=false];
6:ref:h -> 8:data [arrowtail=dot, dir=both, tailclip=false];
8:ref:8->NULL [arrowtail=dot,
dir=both, tailclip=false]
l2 -> 2 [ tailclip=false]
}
```
```graphviz
digraph linked_list{
rankdir=LR
node [shape=record];
1 [label="{ <data> 1 | <ref> }"];
3 [label="{ <data> 3 | <ref> }"];
5 [label="{ <data> 5 | <ref> }"];
7 [label="{ <data> 7 | <ref> }"];
cur[label="{ <data> cur }"];
cur -> "2":ref[tailclip=false]
l1 -> 3 [ tailclip=false]
1:ref:c -> 2:data [arrowtail=dot, dir=both, tailclip=false];
3:ref:c -> 5:data [arrowtail=dot, dir=both, tailclip=false];
5:ref:c -> 7:data [arrowtail=dot, dir=both, tailclip=false];
7:ref:c ->NULL [arrowtail=dot,
dir=both, tailclip=false]
2 [label="{ <data> 2| <ref> }"];
4 [label="{ <data> 4 | <ref> }"];
6 [label="{ <data> 6 | <ref> }"];
8 [label="{ <data> 8 | <ref> }"];
l1 [label="{ <data> l1 }"];
l2 [label="{ <data> l2 }"];
2:ref:f -> 4:data [arrowtail=dot, dir=both, tailclip=false];
4:ref:g -> 6:data [arrowtail=dot, dir=both, tailclip=false];
6:ref:h -> 8:data [arrowtail=dot, dir=both, tailclip=false];
8:ref:8->NULL [arrowtail=dot,
dir=both, tailclip=false]
l2 -> 4 [ tailclip=false]
}
```
```graphviz
digraph linked_list{
rankdir=LR
node [shape=record];
1 [label="{ <data> 1 | <ref> }"];
3 [label="{ <data> 3 | <ref> }"];
5 [label="{ <data> 5 | <ref> }"];
7 [label="{ <data> 7 | <ref> }"];
cur[label="{ <data> cur }"];
cur -> "3":ref[tailclip=false]
l1 -> 5 [ tailclip=false]
1:ref:c -> 2:data [arrowtail=dot, dir=both, tailclip=false];
3:ref:c -> 5:data [arrowtail=dot, dir=both, tailclip=false];
5:ref:c -> 7:data [arrowtail=dot, dir=both, tailclip=false];
7:ref:c ->NULL [arrowtail=dot,
dir=both, tailclip=false]
2 [label="{ <data> 2| <ref> }"];
4 [label="{ <data> 4 | <ref> }"];
6 [label="{ <data> 6 | <ref> }"];
8 [label="{ <data> 8 | <ref> }"];
l1 [label="{ <data> l1 }"];
l2 [label="{ <data> l2 }"];
2:ref:f -> 3:data [arrowtail=dot, dir=both, tailclip=false];
4:ref:g -> 6:data [arrowtail=dot, dir=both, tailclip=false];
6:ref:h -> 8:data [arrowtail=dot, dir=both, tailclip=false];
8:ref:8->NULL [arrowtail=dot,
dir=both, tailclip=false]
l2 -> 4 [ tailclip=false]
}
```
採取部份迭代的方法能順利通過 trace-15 ,但若要從根本上解決問題,程式碼必須全部改成迭代。
## 論文與 simulation 程式碼分析
`is_insert_tail_const` 函式負責分析 `q_insert_tail` 的時間複雜度是不是常數:
```c=
bool is_insert_tail_const(void)
{
bool result = false;
t = malloc(sizeof(t_ctx));
for (int cnt = 0; cnt < test_tries; ++cnt) {
printf("Testing insert_tail...(%d/%d)\n\n", cnt, test_tries);
init_once();
for (int i = 0;
i <
enough_measurements / (number_measurements - drop_size * 2) + 1;
++i)
result = doit(0);
printf("\033[A\033[2K\033[A\033[2K");
if (result == true)
break;
}
free(t);
return result;
}
```
比較重要的是 `init_once` 和 `doit` 兩個函式。
`init_once` 負責待測 `queue` 的初始化,為了避免干擾到真正要進行 `q_insert_tail` 的 `queue` ,會另外生成一個 `queue`:
```c=
static void init_once(void)
{
init_dut();
t_init(t);
}
/* Implement the necessary queue interface to simulation */
void init_dut(void)
{
q = NULL;
}
void t_init(t_ctx *ctx)
{
for (int class = 0; class < 2; class ++) {
ctx->mean[class] = 0.0;
ctx->m2[class] = 0.0;
ctx->n[class] = 0.0;
}
return;
}
```
`doit` 則是包含實際負責測量的函式:
```c=
static bool doit(int mode)
{
int64_t *before_ticks = calloc(number_measurements + 1, sizeof(int64_t));
int64_t *after_ticks = calloc(number_measurements + 1, sizeof(int64_t));
int64_t *exec_times = calloc(number_measurements, sizeof(int64_t));
uint8_t *classes = calloc(number_measurements, sizeof(uint8_t));
uint8_t *input_data =
calloc(number_measurements * chunk_size, sizeof(uint8_t));
if (!before_ticks || !after_ticks || !exec_times || !classes ||
!input_data) {
die();
}
prepare_inputs(input_data, classes);
measure(before_ticks, after_ticks, input_data, mode);
differentiate(exec_times, before_ticks, after_ticks);
update_statistics(exec_times, classes);
bool ret = report();
free(before_ticks);
free(after_ticks);
free(exec_times);
free(classes);
free(input_data);
return ret;
}
```
`prepare_inputs` 負責初始化 `input_data` 、 `classes` 、 `random_string`,其中:
1. `input_data` 有150個數,每個數佔一個 `chunk_size` ,每個數表示對應 `string` 的插入個數。如果 `classes[i]` 對應的值為0(代表 fixed case),則對應 `input_data` 中第 i 個 chunk 的值(插入個數)也會設為0。
2. `classes`為長度 150 的 array , array 的第 i 格的值代表第 i 格的 class 。因為只有兩個 class,故只會有 0 、 1 兩種值。
3. `random_string` 存有 150 個 string ,每個 string 長度為7。
```c=
void prepare_inputs(uint8_t *input_data, uint8_t *classes)
{
randombytes(input_data, number_measurements * chunk_size);
for (size_t i = 0; i < number_measurements; i++) {
classes[i] = randombit();
if (classes[i] == 0)
*(uint16_t *) (input_data + i * chunk_size) = 0x00;
}
for (size_t i = 0; i < NR_MEASURE; ++i) {
/* Generate random string */
randombytes((uint8_t *) random_string[i], 7);
random_string[i][7] = 0;
}
}
```
`measure` 負責取得 insert tail 之前和之後的 CPU Cycle 。
另外,在執行 `dut_insert_tail` 前,有先 insert head。從 `random_string` 取出 array ,從 `input_data` 指向的第 i 個 chunk 取出要 insert 的數量 。
```c=
void measure(int64_t *before_ticks,
int64_t *after_ticks,
uint8_t *input_data,
int mode)
{
assert(mode == test_insert_tail || mode == test_size);
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();
}
} else {
for (size_t i = drop_size; i < number_measurements - drop_size; i++) {
dut_new();
dut_insert_head(
get_random_string(),
*(uint16_t *) (input_data + i * chunk_size) % 10000);
before_ticks[i] = cpucycles();
dut_size(1);
after_ticks[i] = cpucycles();
dut_free();
}
}
}
```
`differentiate` 將其相減後得出 `exe_times` ,送入 `update_statics` , `update_statics` 會根據第 i 個 measurement 對應的 class (也就是 class [ i ]的值) 進行 t-test。
```c=
static void update_statistics(int64_t *exec_times, uint8_t *classes)
{
for (size_t i = 0; i < number_measurements; i++) {
int64_t difference = exec_times[i];
/* Cpu cycle counter overflowed or dropped measurement */
if (difference <= 0) {
continue;
}
/* do a t-test on the execution time */
t_push(t, difference, classes[i]);
}
}
```
`t_push` 負責計算$\Delta{x}$、$\bar{x}$、$\sum\limits_{i = 0}^N{({x_{i}-\bar{x})}^2}$
```c=
void t_push(t_ctx *ctx, double x, uint8_t class)
{
assert(class == 0 || class == 1);
ctx->n[class]++;
/* Welford method for computing online variance
* in a numerically stable way.
*/
double delta = x - ctx->mean[class];
ctx->mean[class] = ctx->mean[class] + delta / ctx->n[class];
ctx->m2[class] = ctx->m2[class] + delta * (x - ctx->mean[class]);
}
```
`report` 中的 `t_compute` 則會計算出 `t`
$t = \frac{\bar{X_0} - \bar{X_1}}{\sqrt{\frac{Var_0}{N_0} + \frac{Var_1}{N_1}}}$
其中:
$Var_{0} = {\sum\limits_{i = 0}^{N_0}{({x_{i}-\bar{x}_{0})}^2}\over{{N_0}-1}}$
$Var_{1} = {\sum\limits_{i = 0}^{N_1}{({x_{i}-\bar{x}_{1})}^2}\over{{N_1}-1}}$
```c=
double t_compute(t_ctx *ctx)
{
double var[2] = {0.0, 0.0};
var[0] = ctx->m2[0] / (ctx->n[0] - 1);
var[1] = ctx->m2[1] / (ctx->n[1] - 1);
double num = (ctx->mean[0] - ctx->mean[1]);
double den = sqrt(var[0] / ctx->n[0] + var[1] / ctx->n[1]);
double t_value = num / den;
return t_value;
}
```
~~另外,仔細查看 `t_push` 的實作部份會發現,每次計算 ${m_2}$(也就是$\sum\limits_{i = 0}^N{({x_{i}-\bar{x})}^2}$)時,所用的$\bar{x}$都不同,但理論上必須使用全體的$\bar{x}$才符合統計學上的意義。
於是對以下檔案進行修改:~~
後來發現wikipedia 上其實有對 Welford's online algorithm 的仔細敘述,並且還有詳細舉出傳統我認知計算 variance 的方法可能會導致 overflow 的問題。
基於上述,以下的修改實屬多餘。
1. `ttest.h` :為 `t_ctx` 新增 `sum[2]` 欄位,記錄對應 class 的 total sum 。
```c=
#ifndef DUDECT_TTEST_H
#define DUDECT_TTEST_H
#include <stdint.h>
typedef struct {
double sum[2];/*member added to keep total sums*/
double mean[2];
double m2[2];
double n[2];
} t_ctx;
void t_push(t_ctx *ctx, double x, uint8_t class);
void t_mean(t_ctx *ctx, int64_t *exec_times, uint8_t *classes);/*calculate mean for exec_times*/
double t_compute(t_ctx *ctx);
void t_init(t_ctx *ctx);
#endif
```
2. `ttest.c` : 修改 `t_push` ,只保留計算 $\sum\limits_{i = 0}^N{({x_{i}-\bar{x})}^2}$ 的部份,將計算mean的工作交給新增的 `t_mean`
```c=
/**
* Online Welch's t-test.
*
* Tests whether two populations have same mean.
* This is basically Student's t-test for unequal
* variances and unequal sample sizes.
*
* see https://en.wikipedia.org/wiki/Welch%27s_t-test
*
*/
#include "ttest.h"
#include <assert.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
extern const size_t number_measurements;
void t_push(t_ctx *ctx, double x, uint8_t class)
{
assert(class == 0 || class == 1);
// ctx->n[class]++;
/* Welford method for computing online variance
* in a numerically stable way.
*/
double delta = x - ctx->mean[class];
// ctx->mean[class] = ctx->mean[class] + delta / ctx->n[class];
ctx->m2[class] = ctx->m2[class] + delta * delta /*(x - ctx->mean[class])*/;
}
/*Use t_mean to calculate the "correct" mean for total sum */
void t_mean(t_ctx *ctx, int64_t *exec_times, uint8_t *classes)
{
for (size_t i = 0; i< number_measurements; i++) {
assert(classes[i] == 0 || classes[i] == 1);
ctx->n[classes[i]]++;
ctx->sum[classes[i]] += exec_times[i];
}
ctx->mean[0] = ctx->sum[0] / ctx->n[0];
ctx->mean[1] = ctx->sum[1] / ctx->n[1];
}
```
3.`fixture.c` : 把 `t_mean` 加到程式碼的開頭,一開始就把 mean 給計算出來。
```c=
static void update_statistics(int64_t *exec_times, uint8_t *classes)
{
t_mean(t, exec_times, classes);
for (size_t i = 0; i < number_measurements; i++) {
int64_t difference = exec_times[i];
/* Cpu cycle counter overflowed or dropped measurement */
if (difference <= 0) {
continue;
}
/* do a t-test on the execution time */
t_push(t, difference, classes[i]);
}
}
```
## TODO LIST
* 用 Valgrind 排除 `qtest` 實作的記憶體錯誤
* 用 Massif 視覺化記憶體使用量
* 設計實驗
* 解釋程式的 "simulation" 模式如何以實驗驗證複雜度