# 2020q3 Homework1 (lab0)
contributed by <`sammer1107`>
###### tags: `進階電腦系統理論與實作`
## 開發環境
```shell
$ uname -a
Linux Aspire-V5-591G 4.15.0-117-generic #118~16.04.1-Ubuntu SMP Sat Sep 5 23:35:06 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
```
# 9/12
## 實作 O(1) q_size
```c=
typedef struct {
list_ele_t *head; /* Linked list of elements */
size_t size;
} queue_t;
```
```c=
int q_size(queue_t *q)
{
if (q == NULL) {
return 0;
} else {
return q->size;
}
}
```
首先和牛刀小試的內容一樣,我藉由在 `queue_t` 中新增一 `size` 變數,來達到常數時間得到 queue 的大小。但同時要確保在刪除、新增 queue 節點時,要記得正確紀錄 `size` 變化。
## 修正 q_new
```c=
queue_t *q_new()
{
queue_t *q = malloc(sizeof(queue_t));
if (q) {
q->head = NULL;
q->size = 0;
}
return q;
}
```
判斷 `q` 非 `NULL` 之後我們才能初始化 queue 並回傳記憶體位置,若為 `NULL` 則按照規則要求回傳 `NULL`。
## 實作 q_free
```c=
void q_free(queue_t *q)
{
if (!q)
return;
while (q->head) {
list_ele_t *next;
next = q->head->next;
free(q->head->value);
free(q->head);
q->head = next;
}
free(q);
}
```
此函式作用就是首先走訪整個 list 並釋放每個節點的字串以及節點本身。最後才釋放 queue 的 struct。
::: warning
:warning: 這時我還沒實作 insert function 中 copy 字串的功能,所以這裡釋放的字串事實上不是歸 queue 管的,所以要儘快完成 copy 的功能。
:::
## 實作 q_insert_head
```c=
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
char *string;
size_t str_len = strlen(s) + 1; // include null terminator
if (q == NULL) {
return false;
}
newh = malloc(sizeof(list_ele_t));
string = malloc(sizeof(char) * str_len);
if (newh == NULL || string == NULL) {
free(newh);
free(string);
return false;
}
strncpy(string, s, str_len);
newh->value = string;
newh->next = q->head;
q->head = newh;
q->size += 1;
return true;
}
```
+ 這裡首先要檢查 q 是否為 `NULL`,若是則按照規則回傳 false。
+ 在來要取得需要的記憶體包含節點本身與字串,而 12~14 行之所以可以一起判斷並一起釋放是根據[文件](https://en.cppreference.com/w/c/memory/free)所說,就算指標是 `NULL` 也可以傳入 `free`
+ 得到記憶體後只需 copy 字串、將節點插在頭並修正 queue 大小即可。
## 實作 q_insert_tail
要達成常數時間在尾巴插入,我們就需要一個指標一直紀錄著尾巴的位置。因此要修改 `queue_t`:
```c=
typedef struct {
list_ele_t *head; /* Linked list of elements */
list_ele_t *tail;
size_t size;
} queue_t;
```
而 `q_insert_tail` 的內容就與 `q_insert_head` 大致相同。下面的 3~18 行都在做一樣的事。
值得注意的事這裡要處理當 list 為空的情況,當 list 為空,頭跟尾都會直接指向新節點;若非空,則將新節點接在後面。
```c=
bool q_insert_tail(queue_t *q, char *s)
{
list_ele_t *new;
char *string;
size_t str_len = strlen(s) + 1; // include null terminator
if (q == NULL) {
return false;
}
new = malloc(sizeof(list_ele_t));
string = malloc(sizeof(char) * str_len);
if (new == NULL || string == NULL) {
free(new);
free(string);
return false;
}
strncpy(string, s, str_len);
new->value = string;
new->next = NULL;
if (q->size == 0) {
q->head = q->tail = new;
} else {
q->tail->next = new;
q->tail = new;
}
q->size += 1;
return true;
}
```
因為增加了新的 `tail` 指標要維護,原本的 `q_insert_head` 也要做對應的改變,在 21 行後改成:
```c=21
if (q->size == 0) {
q->tail = q->head;
}
q->size += 1;
return true;
```
## 實作 q_remove_head
```c=
bool q_remove_head(queue_t *q, char *sp, size_t bufsize)
{
if (!q || !q->head)
return false;
list_ele_t *removed = q->head;
snprintf(sp, bufsize, "%s", removed->value);
if (q->size == 1) {
q->tail = q->head = NULL;
} else {
q->head = q->head->next;
}
list_element_free(removed);
q->size -= 1;
return true;
}
```
```c=
void list_element_free(list_ele_t *ele)
{
free(ele->value);
free(ele);
}
```
+ 3~4: 若 `q` 為 NULL 或 list 為空則回傳 false
+ 6~8: 取得要移除的元素並把字傳複製回 buffer
+ 10~14:
1. 若 list 剩一個元素,則同時要更動 head 與 tail,因為他們目前都指到此元素
2. 若有兩個或更多,則只需要更動頭即可
+ 16: 這裡我發現 remove 與 free 的函式都需要釋放節點,所以我把他寫成一個函式,方便日後擴充
# 9/14
## 實作 q_reverse
```c=
void q_reverse(queue_t *q)
{
list_ele_t *cursor = NULL;
q->tail = q->head;
while (q->head) {
list_ele_t *tmp = q->head->next;
q->head->next = cursor;
cursor = q->head;
q->head = tmp;
}
q->head = cursor;
}
```
這裡使用和 quiz1 中一樣的實作
## 實作 q_sort
```c=
void q_sort(queue_t *q)
{
if (!q)
return;
q->head = merge_sort_list(q->head);
// update tail
q->tail = q->head;
while (q->tail->next) {
q->tail = q->tail->next;
}
}
list_ele_t *merge_sort_list(list_ele_t *head)
{
// NULL or single node
if (!head || !head->next) {
return head;
}
// split list
list_ele_t *first = head, *second = head;
while (second->next && second->next->next) {
second = second->next->next;
first = first->next;
}
second = first->next;
first->next = NULL;
first = merge_sort_list(head);
second = merge_sort_list(second);
return merge_sorted(first, second);
}
list_ele_t *merge_sorted(list_ele_t *head1, list_ele_t *head2)
{
list_ele_t *new_head;
list_ele_t **indirect=&new_head;
while (head1 && head2) {
if (strcmp(head1->value, head2->value) < 0) {
*indirect = head1;
head1 = head1->next;
} else {
*indirect = head2;
head2 = head2->next;
}
indirect = &(*indirect)->next;
}
if (head1) {
*indirect = head1;
} else {
*indirect = head2;
}
return new_head;
}
```
### q_sort 函式
+ 此函式只檢查 `q` 是否為 `NULL`,至於 list 可能為空或元素只有一個的情況則會交由 `merge_sort_list()` 處理
+ 值得注意的是,`merge_sort_list` 只會回傳新的頭,因此要在 8~10 行重新去找找新的尾巴。
### merge_sort_list 函式
+ 先檢查是否為空或是單個元素,若是,則已經排序好,直接回傳。
+ 若不是:
+ 22~28: 這裡的作用在於把 list 對分成兩半。我們使用一個一次走一格的指標 `first` 與令一個一次走兩格的指標 `second`。當 `second` 走到底時,`first` 差不多走到中間,因此第2段的開頭設定在 `second` 的下一節點,而第1段的開頭則是原本的 `head`。
+ 30~31: 對第1段與第2段繼續做遞迴呼叫,持續分割並排序
+ 33: 合併兩已排序的 list 並回傳新的頭
### merge_sorted 函式
+ 38~39: 這裡我先創造了一個指標 `new_head` 用來指向新的頭,以及指標的指標 `indirect`。藉由使用指標的指標的技巧,可以不必為第一個節點寫專屬的 code。
+ 41~49: 比較兩個list的節點,小的就拉過來接到新的 list 最後面。
+ 52~55: 當有一個 list 先走完時,我們把剩下的那條直接接過來。
+ 58: 最後回傳新的頭位置
## 完成實作後第一次完整的測試
這次測試我只拿了 76 分,我發現原來是少處理了很多例外狀況:
+ ### Test operations on NULL queue
+ 手動執行對應的測試: `./qtest -f traces/trace-07-robust.cmd`
+ 發現原來是 reverse 忘記檢查 `q` 是否為 `NULL`
+ 加上
```c
if (!q)
return;
```
在 reverse 開頭即可
+ ### Test operations on empty queue
+ 手動執行對應的測試: `./qtest -f traces/trace-08-robust.cmd`
+ 發現是原先的 sort 並沒有很好的處理 empty queue 會在第 9 行 dereference 了 null pointer
```c=7
// update tail
q->tail = q->head; // NULL here
while (q->tail->next) {
q->tail = q->tail->next;
}
```
+ 所以 `q_sort` 的 3~4 應該改成:
```c=3
if (!q || !q->head)
return;
```
來包含為空的情況
+ 如此在 `merge_sort_list()` 中也不需要檢查是否為 `NULL` 了,因為我很確定目前切割的方式不會切出 `NULL`
```c=16
// single node
if (!head->next) {
return head;
}
```
+ ### Test of malloc failure on insert_head & malloc failure on insert_tail
+ 我發現原本的程式碼應該是會正確的回傳 `false` 的,但由於我把創建新節點的程式碼變成一個函式了,再這之後我反而忘記在函式回傳後檢查回傳值,造成 test fail。
+ 修改後的程式碼長這樣(insert tail 同理):
```c
list_ele_t *list_element_new(char *s)
{
size_t len = strlen(s) + 1; // include null terminator
list_ele_t *new = malloc(sizeof(list_ele_t));
if (new == NULL) {
return NULL;
}
new->value = malloc(sizeof(char) * len);
if (new->value == NULL) {
free(new);
return NULL;
}
new->next = NULL;
strncpy(new->value, s, len);
return new;
}
bool q_insert_head(queue_t *q, char *s)
{
list_ele_t *newh;
if (q == NULL) {
return false;
}
newh = list_element_new(s);
if (!newh)
return false;
newh->next = q->head;
q->head = newh;
if (q->size == 0) {
q->tail = q->head;
}
q->size += 1;
return true;
}
```
### 修改完以上就成功得到 :100: 分了。
# 9/15
## 修正記憶體錯誤
+ 當我開啟 AddressSanitizer 後發現最後一個 case 會無法通過:
```shell
$ make SANITIZER=1
$ ./qtest -f traces/trace-17-complexity.cmd
cmd> # Test if q_insert_tail and q_size is constant time complexity
cmd> option simulation 1
=================================================================
==8662==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000614540 at pc 0x000000406304 bp 0x7fffee21b590 sp 0x7fffee21b580
READ of size 4 at 0x000000614540 thread T0
#0 0x406303 in do_option_cmd /home/sammer/guts/hw1_lab0/console.c:368
#1 0x405007 in interpret_cmda /home/sammer/guts/hw1_lab0/console.c:220
#2 0x405a2e in interpret_cmd /home/sammer/guts/hw1_lab0/console.c:243
#3 0x406563 in cmd_select /home/sammer/guts/hw1_lab0/console.c:569
#4 0x40697d in run_console /home/sammer/guts/hw1_lab0/console.c:628
#5 0x403eb2 in main /home/sammer/guts/hw1_lab0/qtest.c:772
#6 0x7f6195a3483f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2083f)
#7 0x401af8 in _start (/home/sammer/guts/hw1_lab0/qtest+0x401af8)
0x000000614541 is located 0 bytes to the right of global variable 'simulation' defined in 'console.c:20:6' (0x614540) of size 1
'simulation' is ascii string ''
SUMMARY: AddressSanitizer: global-buffer-overflow /home/sammer/guts/hw1_lab0/console.c:368 do_option_cmd
Shadow bytes around the buggy address:
0x0000800ba850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800ba860: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800ba870: 00 00 00 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9
0x0000800ba880: 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
0x0000800ba890: 00 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
=>0x0000800ba8a0: 00 f9 f9 f9 f9 f9 f9 f9[01]f9 f9 f9 f9 f9 f9 f9
0x0000800ba8b0: 01 f9 f9 f9 f9 f9 f9 f9 04 f9 f9 f9 f9 f9 f9 f9
0x0000800ba8c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0000800ba8d0: 00 00 00 00 00 00 00 00 00 f9 f9 f9 f9 f9 f9 f9
0x0000800ba8e0: 01 f9 f9 f9 f9 f9 f9 f9 01 f9 f9 f9 f9 f9 f9 f9
0x0000800ba8f0: 04 f9 f9 f9 f9 f9 f9 f9 00 f9 f9 f9 f9 f9 f9 f9
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
==8662==ABORTING
```
+ 追查到對應的程式碼位置 `do_option_cmd in console.c:368`,發現應該是在存取 valp 的時候出現了問題。
```c=366
while (!found && plist) {
if (strcmp(plist->name, name) == 0) {
int oldval = *plist->valp;
*plist->valp = value;
if (plist->setter)
plist->setter(oldval);
found = true;
} else
plist = plist->next;
}
```
+ 由於出錯的指令是 `option simulation 1`,所以我追查到產生 `param_ele` 的定義:
```
typedef struct PELE param_ele, *param_ptr;
struct PELE {
char *name;
int *valp;
char *documentation;
/* Function that gets called whenever parameter changes */
setter_function setter;
param_ptr next;
};
```
發現 valp 應該是要是一個指向 `int` 的指標,但在 `console.c:102` 的地方我們把一個 bool 的位置轉型成了 `int*` 硬傳進去。
```c=20
bool simulation = false;
```
```c=102
add_param("simulation", (int *) &simulation, "Start/Stop simulation mode",NULL);
```
+ 在我的編譯器上 `sizeof(bool)` 的結果為 1,因此當我們 dereference `valp`,會讀取出界,因為 `sizeof(int)` 為 4。
+ 同理在 echo 的選項設定也有一樣的問題。
```c=107
add_param("echo", (int *) &echo, "Do/don't echo commands", NULL);
```
+ 將 `simulation` 與 `echo` 都改成 int 型態就解決上述問題了。
## Massif 視覺化
我直接使用 test case 17 來觀察記憶體使用狀況。
**檔案內容:**
```
# Test if q_insert_tail and q_size is constant time complexity
option simulation 1
it
size
option simulation 0
```
**測試命令:**
```shell
$ valgrind --tool=massif ./qtest -f traces/trace-17-complexity.cmd
```
![](https://i.imgur.com/7aQhlld.jpg)
:::warning
大部分的趨勢線是緩和變化的,這是因為預設模式下,要好幾個 snapshot 才會有一個 detailed snapshot,所以大部分的線中間都是因為沒有紀錄的所以是直線。正常來講 test_malloc 跟 total heap 應該要同上同下。
:::
藉由調高 snapshot 次數到 200 以及 detailed snapshot 頻率到 3,我們可以看到 simulation 更完整的模樣: 不斷的配置大小不一樣的 queue 做測試,導致 heap 空間劇烈變化。
![](https://i.imgur.com/dJjriYG.jpg)
# Dude, is my code constant time? 閱讀
## 論文的動機
+ 非常數時間的演算法容易遭遇 timing attack,會造成安全威脅
+ 儘管理論上應該是常數時間的演算法,也可能因為實作方式而變得不是常數時間,因此需要作驗證
+ 做驗證並不容易,既有工具往往需要對硬體有一定的假設
+ **dudect 單純使用統計方法,可避免因硬體差異造成的麻煩**
## Simulation 實作
從函式 `do_insert_tail()` 與 `do_size()`,我們可以看出 simulation 的起點在函式 `is_insert_tail_const()` 與 `is_size_const()`。
以 `is_size_const()` 為例:
```c=177
bool is_size_const(void)
{
bool result = false;
t = malloc(sizeof(t_ctx));
for (int cnt = 0; cnt < test_tries; ++cnt) {
printf("Testing size...(%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(1);
printf("\033[A\033[2K\033[A\033[2K");
if (result == true)
break;
}
free(t);
return result;
}
```
#### 變數說明
+ t_ctx: 這個變數用來紀錄兩個 class 目前的 sample 數、平均值、與平均的差的平方的總合(sum of squares of differences),之後會用 Welford method 來更新這些值
```c
typedef struct {
double mean[2]; // mean of each class
double m2[2]; // sum of squared errors
double n[2]; // number of sample in each class
} t_ctx;
```
#### 程式碼
+ (181) 第一個迴圈: 這裡的作用在於假如做完 `enough_measurement` 個測量之後,都測出來非常數時間,就再試一次,直到試了 `test_tries` 次。如果做了 `test_tries` 次都否決 null hypothesis,我們才說他是非常數時間。
+ (184) 第二個回圈: 每次進迴圈會做 `number_measurements - drop_size * 2` 次測量(在 `doit()` 內),直到做了 **至少** `enough_measurements` 個測量。
### doit()
```c=120
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;
}
```
#### 變數說明
+ `before_ticks`: 用來紀錄每一個 measure 前的 cycle count
+ `after_ticks`: 用來紀錄每一個 measure 後的 cycle count
:::danger
:warning: 這裡 `before_ticks` 與 `after_ticks` 皆要了 `number_measurements + 1` 的空間,這應該是**錯的**。由於我們已經把開始與結束時間分開紀錄,就不需要多一個空間。
:eye: 觀察原作者的 dudect 專案,他之所以需要 `+1` ,是因為他只紀錄每一個 measurement 開始的時間,並用下一次開始的時間當作上一次結束的時間,因此才需要多一個來紀錄最後一次結束的時間。
:::
+ `exec_times`: 藉由 `before_ticks` 和 `after_ticks` 可以算出執行時間
+ `classes`: 用來紀錄每一個 measurement 是屬於哪一個 class ,由於要確保時間等外在因素的干擾,所以才要隨機的混合兩種 class 的測量。
+ `input_data`: 用來裝隨機產生的 bytes,這些隨機資料會決定之後測試 random class 時,要先在頭插入多少節點,才測試 `insert_tail` 和 `size`。
### prepare_input()
```c=39
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;
}
}
```
#### 程式碼
+ 41行: 這裡我們總共產生了 `number_measurements * chunk_size` 個 random byte 。其中 chunk size 為 16 (byte),但我們之後在 45 行及 measure 中用到的時候是用 2 byte 而已:
+ measure()
```c=
dut_insert_head(
get_random_string(),
*(uint16_t *) (input_data + i * chunk_size) % 10000);
```
:::danger
:warning: 意思就是我們每產生 16 個 byte,實際用到的只有 2 byte (uint16_t 為 16 bit)。**我想 chunk_size 應該設成 2 即可**,除非是隨機上的考量?我直覺上是不需要有額外的 byte 的
:::
+ 42~46: 這裡我們才逐一個決定每一個 measurement 的 class,如果是 fixed-class (=0) ,就在把對應的 chunk 設成 0,到時候測試時就不會在頭插入任何節點。
+ 48~52: 這裡則是產生 number_measurement 個隨機字串長度的部份,每個字串長度 7 加上一個 null terminator。
:::warning
這裡使用的巨集 NR_MEASURE 與 `number_measurements` 是同值,但為了讓程式碼更一致,也許應該用 `number_measurements` 就好?
:::
### measure()
```c=55
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();
}
}
}
```
#### 程式碼
+ 測試 insert tail (61~72)
原理與下面 size 差不多
+ 測試 size (74~83)
這裡總共要做 `number_measurements - drop_size*2` 次的測試,每次的測試步驟如下:
1. 根據前面隨機的 `input_data` ,我們插入隨機個一樣的字串到頭
2. 紀錄目前 cycle
3. 執行要測試的函式
4. 紀錄結束的 cycle
:::danger
:warning: 這裡可以看到在前面 `drop_size` 個跟後面 `drop_size` 個的 `input_data` 其實都沒用到。 我想 drop 的動作應該是**基於統計的考量?** 但就空間而言應該有更經濟的作法: 不儲存這些 drop 的 `input_data` 以及對應的 `before_ticks`,`after_ticks`,`exec_times`,`classes`。
:::
### differentiate()
```c=61
static void differentiate(int64_t *exec_times,
int64_t *before_ticks,
int64_t *after_ticks)
{
for (size_t i = 0; i < number_measurements; i++) {
exec_times[i] = after_ticks[i] - before_ticks[i];
}
}
```
這個函式是計算執行時間,結束時間減開始時間即是執行時間。
:::danger
:warning: 這裡我發現,這個步驟其實可以在 measure 中就做完,如此我們只需要一個 exec_times 即可,根本不需要多花費 2 倍的空間 (before_ticks, after_ticks) 來存放數字。
:::
### update_statistics()
```c=70
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]);
}
}
```
這個函式把所有有效的測量(去除 dropped 與剛好遇到 counter overflow的情況),都傳入 t_push:
```c=19
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]);
}
```
t_push 會使用 Welford method,來計算加入新 sample 後對應的平均、與平均的差平方總和與 sample 數量。
### report()
```c=83
static bool report(void)
{
double max_t = fabs(t_compute(t));
double number_traces_max_t = t->n[0] + t->n[1];
double max_tau = max_t / sqrt(number_traces_max_t);
printf("\033[A\033[2K");
printf("meas: %7.2lf M, ", (number_traces_max_t / 1e6));
if (number_traces_max_t < enough_measurements) {
printf("not enough measurements (%.0f still to go).\n",
enough_measurements - number_traces_max_t);
return false;
}
```
```c=108
printf("max t: %+7.2f, max tau: %.2e, (5/tau)^2: %.2e.\n", max_t, max_tau,
(double) (5 * 5) / (double) (max_tau * max_tau));
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;
}
}
```
#### 程式碼
+ 85: 這裡跟據公式計算 t value 的絕對值,會取絕對值是因為我們做的是 two-tailed test。
$$
t = \frac{\bar{X_0} - \bar{X_1}}{\sqrt{\frac{s^2_1}{N_1} + \frac{s^2_2}{N_2}}}
$$
+ 111 與 113 的作用在於檢查 t 是否大於一定值。當 t value 絕對值過大,說明 fixed 與 random 兩個 class 的 mean 有很大的可能並不相同,否決了 null hypothesis (null 為兩個 distribution 一樣)
+ t_threshold_bananas: 非常誇張的標準,如果 t 到了這麼大,非常確定是非常數時間。
+ t_threshold_moderate: 一個比較合理的標準
+ 若 115 成立則代表無法否決 null hypothesis,t 過小,代表兩個平均值的差異可能只是隨機的。
### 結尾
我們已經走過了整個流程,回到 `is_size_const`:
```c=177
bool is_size_const(void)
{
bool result = false;
t = malloc(sizeof(t_ctx));
for (int cnt = 0; cnt < test_tries; ++cnt) {
printf("Testing size...(%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(1);
printf("\033[A\033[2K\033[A\033[2K");
if (result == true)
break;
}
free(t);
return result;
}
```
+ 若做完 `number_measurements` 後,若 `doit` 回傳 `true`,則代表這次檢驗出來 null hypothesis 成立,應該為常數時間。回傳到 `do_size` 後程式印出 Probably constant time。
+ 若做完後 `doit` 回傳 `false`,則代表這次檢驗出來 null hypothesis 被否決,應該為非常數時間。但我們總共有 `test_tries` 次機會,如果都失敗程式才會回傳並印出 Probably not constant time
## Simulation 實作缺陷
作者在論文中提到了要做 post-processing,但程式碼中似乎沒處理到這部份。