owned this note
owned this note
Published
Linked with GitHub
---
tags: linux kernel
---
# 2024q1 Homework1 (lab0)
contributed by < [williamlin0518](https://github.com/williamlin0518) >
## 開發環境
```shell
$ gcc --version
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.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: 46 bits physical, 48 bits virtual
Byte Order: Little Endian
CPU(s): 20
On-line CPU(s) list: 0-19
Vendor ID: GenuineIntel
Model name: 12th Gen Intel(R) Core(TM) i7-12700K
CPU family: 6
Model: 151
Thread(s) per core: 2
Core(s) per socket: 10
Socket(s): 1
Stepping: 2
BogoMIPS: 7219.20
```
## 指定的佇列操作
### `q_new`
:::danger
allocate 的翻譯是「配置」,以區格 dispatch (分配/分發) 的翻譯。
:::
如果空間<s>分配</s> 失敗 回傳NULL
使用函式 INIT_LIST_HEAD 初始化
:::danger
改進你的漢語表達。
:::
```c
struct list_head *q_new() {
struct list_head *head = malloc(sizeof(struct list_head));
if (!head) return NULL;
INIT_LIST_HEAD(head); // Initialize the list to point to itself
return head;
}
```
:::danger
使用指定的程式碼縮排風格。
:::
### q_free
藉由 `list_entry` 巨集,從 list_head 推導出 `element_t` 結構開頭地址。移除該節點並釋放其空間
深入`list_entry`巨集,發現是根據 `container_of` 和 `offsetof` 算出偏移量
`offsetof` : 將結構的起始位址設為0,得到的MEMBER位址實際上就等於偏移量
```c
#define offsetof(TYPE, MEMBER) ((unsigned int) &((TYPE *)0)->MEMBER)
```
`container_of` : 從 `current` 的位址中減去 `member` 的偏移量,得到包含該 list 成員的 `element_t`的起始位址。
```c
#define container_of(ptr,type,member)\
__extension__({
const __typeof__(((type *)0)->member) *__pmember =(ptr);
(type*)((char*) __pmember - offsetof(type,member));
})
```
```c
void q_free(struct list_head *head) {
if (!head) return;
struct list_head *current, *tmp;
element_t *element;
list_for_each_safe(current, tmp, head) {
element = list_entry(current, element_t, list);
list_del(current); // Remove from list
free(element->value);
free(element);
}
free(head); // Free the queue head
}
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::
### q_insert_head & q_insert_tail
使用函式 `list_add` 將節點加在 head 的下一個位置
:::info
Before list_add: HEAD -> A -> B -> HEAD
After list_add(new, HEAD): HEAD -> NEW -> A -> B -> HEAD
:::
使用函式 `list_add_tail` 將節點加在 head 的前一個位置
:::info
Before list_add_tail: HEAD -> A -> B -> HEAD
After list_add_tail(new, HEAD): HEAD -> A -> B -> NEW -> HEAD
:::
```c
bool q_insert_head(struct list_head *head, char *s)
{
if (!head || !s)
return false;
element_t *new_element = malloc(sizeof(element_t));
if (!new_element)
return false;
new_element->value = strdup(s);
if (!new_element->value) {
free(new_element);
return false;
}
list_add(&new_element->list, head); // Insert at head
return true;
}
```
### q_remove_head & q_remove_tail
這邊要注意的是`remove` 和 `delete` 的差別,別把資料free掉
移除第一個元素 (head->next) 並且將移除的資料複製到 buffer 中
```c
element_t *q_remove_head(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
struct list_head *first = head->next;
element_t *element = list_entry(first, element_t, list);
list_del(first); // Remove from list
if (sp) {
strncpy(sp, element->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
return element;
}
```
移除最後一個元素 (head->prev) 並且將移除的資料複製到 buffer 中
```c
element_t *q_remove_tail(struct list_head *head, char *sp, size_t bufsize)
{
if (!head || list_empty(head))
return NULL;
struct list_head *last = head->prev; // Assuming 'head' is circular
element_t *element = list_entry(last, element_t, list);
// Copy string to provided buffer
if (sp) {
strncpy(sp, element->value, bufsize - 1);
sp[bufsize - 1] = '\0';
}
// Unlink and return the element
list_del(last);
return element;
}
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::
### q_size
使用 `list_for_each` 遍歷整個佇列
### q_delete_mid
原先使用 `q_size` 得到佇列大小,再遍歷一遍將中間值刪除
發現可以使用快慢指標,這樣可以減少一次遍歷
```c
struct list_head *fast = head->next, *slow = head->next;
while (fast != head && fast->next != head) {
fast = fast->next->next;
slow = slow->next;
}
```
或使用指標分別往前 (forward) 及往後 (backward) 走,並找到中間的節點
```c
struct list_head *forward = head->next;
struct list_head *backward = head->prev;
while (forward != backward && forward->prev != backward) {
forward = forward->next;
backward = backward->prev;
}
```
### q_delete_dup
在排序的佇列中,移走重複內容的節點,針對每個元素,向後尋找擁有相同字串的元素。
```c
bool q_delete_dup(struct list_head *head) {
if (!head || list_empty(head) || list_is_singular(head)) {
return false;
}
element_t *current_element, *next_element;
list_for_each_entry_safe(current_element, next_element, head, list) {
bool is_duplicate = false;
// Keep removing next elements as long as they are duplicates
while (&next_element->list != head && !strcmp(current_element->value, next_element->value)) {
list_del(&next_element->list);
free(next_element->value);
free(next_element);
is_duplicate = true;
next_element = list_entry(current_element->list.next, element_t, list);
}
// If the current element was part of a duplicate sequence, remove it as well
if (is_duplicate) {
list_del(¤t_element->list);
free(current_element->value);
free(current_element);
}
}
return true ;
}
```
### q_reverse q_reverseK
使用 `list_cut_position` 將要反轉的部份切出來,反轉後再用 `list_splice_init` 接回原本佇列
這邊需要仔細看過 `list_cut_position` 和 `list_splice_init` 才能了解如何串接,並注意裁切過後的位置,起初犯了一個錯誤,將`*next_segment_start` 設為 `cut_point->next` 便會少切一個位置
```c
while (start->next != head) {
struct list_head *cut_point = start;
int count = 0;
for (int i = 0; i < k-1 && cut_point->next != head; i++) {
cut_point = cut_point->next;
count++;
}
if (count< k-1) {
break; // Less than k elements left, no more reversing
}
struct list_head *next_segment_start = cut_point->next; // Store the start of the next segment.
INIT_LIST_HEAD(&temp_head);
list_cut_position(&temp_list, start, cut_point);
q_reverse(&temp_list);
list_splice_init(&temp_list, start);
start = next_segment_start;
}
```
更新後的版本,參考 [yeh-sudo](https://hackmd.io/@yehsudo/linux2024-homework1) 及 [pao0626](https://hackmd.io/@dYc__gtWRkqGvuNcfK1ZJA/linux2024-homework1) 後發現可以利用`list_for_each_safe` 將cut_point的後指標存起。
```c
list_for_each_safe (cut_point, safe, head) {
count++;
if (count == k) {
list_cut_position(&temp_list, start, cut_point);
q_reverse(&temp_list);
list_splice_init(&temp_list, start);
count = 0;
start = safe->prev;
}
}
```
### sort
use `list_move_tail` 來排序佇列
```c
void merge(struct list_head *head, struct list_head *left, struct list_head *right, bool descend) {
while (!list_empty(left) && !list_empty(right)) {
element_t *left_entry = list_entry(left->next, element_t, list);
element_t *right_entry = list_entry(right->next, element_t, list);
bool condition = descend ?
(strcmp(left_entry->value, right_entry->value) >= 0) :
(strcmp(left_entry->value, right_entry->value) <= 0);
if (condition) {
list_move_tail(left->next, head);
} else {
list_move_tail(right->next, head);
}
}
// Append any remaining elements
if (!list_empty(left)) {
list_splice_tail(left, head);
}
if (!list_empty(right)) {
list_splice_tail(right, head);
}
}
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::
這邊要注意 `list_cut_position(&left, head, slow);` 會將head指向斷點右側,須將head段開,並使用`right` 指向右側佇列
:::danger
你如何確保目前的測試涵蓋排序演算法的最差狀況?
:::
### `q_ascend`, `q_descend`
這邊的想法是利用雙向鏈結特性,只要往回走即可
```c
int q_ascend(struct list_head *head)
{
if (list_empty(head) || list_is_singular(head)) {
return 0; // No action needed if the list is empty or has only one
// node.
}
int count = 0;
struct list_head *prev, *node = head->prev;
element_t *last_entry = list_entry(node, element_t, list);
char *min_value = last_entry->value;
while (node != head) {
element_t *entry = list_entry(node, element_t, list);
prev = node->prev; // Save the previous node before potentially
// deleting the current node.
if (strcmp(entry->value, min_value) > 0) {
list_del(node);
free(entry->value);
free(entry);
} else {
min_value = entry->value;
count++; // Increment the count of nodes that were not removed.
}
node = prev;
}
return count; // Return the count of removed nodes.
}
```
### q_merge
:::danger
- [ ] traverse (動詞) 和 traversal (名詞)
根據 Dictionary.com 的[解釋](https://www.dictionary.com/browse/traverse
): (作為及物動詞和不及物動詞都有類似的意思,以下列出作為及物動詞的寓意)
* to pass or move over, along, or through.
* to go to and fro over or along.
其實這意思很好懂,就像我們「走過」/「穿越」校園一般,於是 traverse a linked list 就會是「(用某種手段) 存取多個鏈結串列的節點」,但這裡卻沒有必要「所有」的範圍:英語的 "move over/through" 用於某個區域時,根本沒有這樣的隱含意義。如果將 traverse 翻譯為「遍歷」,就會導致「超譯」,也就是跳脫「直譯」和「意譯」。
當我們回頭看 "traverse" 所在的技術描述內容,例如 "traverse every node",若翻譯為「遍歷每個節點」,那麼既然都「遍」(意即「全面」、「到處」),又何來「每個」節點呢?於是,合理的翻譯應改為「逐一走訪每個節點」 —— 差異在哪?在 "traverse every node" 的應用場景中,可能是我們嘗試在鏈結串列尋找特定的節點內含的資料,一旦找到就停止,或者我們要偵測給定的鏈結串列是否包含環狀 (circular) 結構 ,並沒有真的要「遍」(到處/全面)「歷」(意即「經過」) 每個節點。在我們的用語中,要區分「意圖」(intention) 和「實際作用」(reaction),濫用「遍歷」會使得語意不清,從而難以推測英語原文的訴求。
還有個更重要的原因是,「遍歷」這詞已在理工領域存在,且廣泛使用,即「遍歷理論」(Ergodic theory),後者是研究具有不變測度 (invariant measure) 的動力系統及其相關問題的一個數學分支。 遍歷理論研究遍歷變換,由試圖證明統計物理中的遍歷假設 (Ergodic hypothesis) 演進而來。
在統計學中,若單一個物理系統在不同時間內重複相同的實驗 (如丟擲一枚硬幣),其取樣數據所得的統計結果 (如硬幣出現正面的機率) 和極多個完全相同的物理系統的系集 (如丟極多個相同的硬幣) 在同時作相同的實驗取樣數據的統計結果假設為相同時,此種假設即稱為「遍歷性假設」或「遍歷假設」。基於這個假設,對時間平均的統計方式及結果,便可由對系集的平均及統計方式得到。在一般物理系統中,尤其在統計力學範圖中,均採用此遍歷性假設為基本的假設。在流體力學中對亂流的實驗分析,亦是採用此假設。
遍歷 (Ergodic) 源於以下二個希臘詞:
* ergon (對應英語的 work)
* odos (對應英語的 path 或 way)
最初這是由奧地利物理學家波茲曼 ([Ludwig Boltzmann](https://en.wikipedia.org/wiki/Ludwig_Boltzmann)) 於統計力學領域 (statistical mechanics) 提出的概念,其一廣為人知的結果是:在經過長時間後,時間平均將會趨近空間平均。此事實在動力系統扮演極重要的角色,在隨機分析領域亦然。
因此,若貿然將 traverse 翻譯為「遍歷」,一來語意不清,二來增加和理工多項領域專業人員的溝通成本,實在得不償失。
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
這邊我善用了在 `q_sort` 裡使用的 `merge` 函式,所以我<s>遍歷</s> 所有佇列,並且倆倆合併
```c
int q_merge(struct list_head *head, bool descend) {
if (list_empty(head) || list_is_singular(head)) {
return 0; // No action needed if the list is empty or has only one element.
}
queue_contex_t *first_qctx = list_first_entry(head, queue_contex_t, chain);
// Prepare a temporary list for iterative merging.
struct list_head new_head;
INIT_LIST_HEAD(&new_head);
struct list_head merged;
INIT_LIST_HEAD(&merged);
queue_contex_t *entry, *safe;
// Start with merging into the first queue.
list_splice_init(first_qctx->q, &merged);
// Iterate through the remaining queue contexts for merging.
list_for_each_entry_safe(entry, safe, head, chain) {
if (entry == first_qctx) continue; // Skip the first queue context as it's already included.
// Use the merge function to combine the current queue with the merged results.
struct list_head temp;
INIT_LIST_HEAD(&temp);
list_splice_init(entry->q, &temp); // Prepare the current queue.
merge(&new_head, &merged, &temp, descend);
INIT_LIST_HEAD(&merged);
list_splice_init(&new_head, &merged);
}
list_splice(&merged, first_qctx->q);
return q_size(first_qctx->q); z
}
```
:::danger
HackMD 不是讓你張貼完整程式碼的地方,GitHub 才是!因此你在開發紀錄只該列出關鍵程式碼 (善用 diff 標示),可附上對應 GitHub commit 的超連結,列出程式碼是為了「檢討」和「便於他人參與討論」,不是用來「假裝自己有付出」
:::
## 比較自己實作的 merge sort 和 Linux 核心程式碼之間效能落差
### 參考筆記
[kdnvt](https://hackmd.io/@sysprog/linux2022-sample-lab0) [chiangkd](https://hackmd.io/erWlfVMfQqyUe9JVbOLlBA?view#list_sort-%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E6%9C%80%E4%BD%B3%E5%8C%96)
先新增 `list_sort.c` 和 `list_sort.c` 到專案中,更改 `qtest.c`
使其能測量 cpu cycles
```c
if (current && exception_setup(true)) {
before_ticks = cpucycles();
q_sort(current->q, descend);
// list_sort(NULL, current->q, &cmp);
after_ticks = cpucycles();
report_noreturn(0, "cpucycles : %d", after_ticks - before_ticks);
report_noreturn(0, "\n");
}
```
```
cmd> new
cmd> it RAND 100000
cmd> time sort
```
### 實驗數據比較
|sort |cpu cycles |time |list length|
|--------|--------------|---------------|----|
|merge sort|5679844|0.002|10000
|list_sort|4700340|0.002|10000
|merge sort|138283242|0.087|100000
|list_sort|125464132|0.076|100000
### list_sort 的
<s>優化</s>
:::danger
些濫用「優化」的語境,根本與「改良」和「改善」無異,那為何不直接講「改良」和「改善」呢?
$\to$ [資訊科技詞彙翻譯](https://hackmd.io/@sysprog/it-vocabulary)
:::
list_sort 透過迭代而非<s>遞歸</s> 遞迴的方式來合併排序好的子列表,在排序過程中,它將待排序的元素轉換成單向的列表,因此簡化了合併操作
:::danger
注意用語!
:::
迭代方法:通過一系列迭代步驟來合併子列表,逐步建立最終的排序好的列表。這種方法避免了遞迴呼叫的資源,特別是在處理大列表時可能更高效。
```
[4, 2, 1, 3]
初始化:將列表轉換為單向列表 [4 -> 2 -> 1 -> 3 -> NULL]。
第一輪:對每個元素進行處理,創建大小為 1 的子列表,並在必要時合併它們。
處理 4:子列表 [4]。
處理 2:子列表 [2],與 [4] 合併得到 [2 -> 4]。
處理 1:子列表 [1]。
處理 3:子列表 [3],與 [1] 合併得到 [1 -> 3]。
第二輪:合併上一輪創建的子列表。
將 [2 -> 4] 與 [1 -> 3] 合併得到最終排序好的列表 [1 -> 2 -> 3 -> 4]。
每一步的合併操作都是通過迭代完成的,不需要遞迴呼叫。
```
:::danger
注意用語!
:::
<s>遞歸</s> 方法:通過不斷分割<s>列表</s> ??? 並<s>遞歸</s> 排序各個部分,然後合併這些部分來建立最終的排序好的<s>列表</s> ???。這種方法在概念上較為直觀,但可能因遞迴呼叫的資源而在某些情況下效率較低。
---
## 閱讀論文〈[Dude, is my code constant time?]
[Dude, is my code constant time? ](https://eprint.iacr.org/2016/1123.pdf)提出一項檢測程式複雜度是否為常數的工具,步驟如下:
### Step 1: Measure execution time
進行測試的時候分別利用兩組測資,一組是固定測資(全部固定為某個值),另一組則是隨機測資,並對測量結果進行統計分析,以確定兩個輸入資料類別的時序分佈在統計上是否不同。
### Step 2: Apply post-processing
在統計分析之前,對每個單獨的測量值進行後處理,包含:
<s>
#### 1. Cropping
- Objective: To mitigate the impact of outliers and skewness in the timing data, which can be caused by various factors such as system interrupts, measurement artifacts
- Process: Cropping involves setting a threshold (usually determined by a percentile of the data) and discarding measurements that exceed this threshold.
#### 2. Higher-Order Preprocessing
- Objective: To detect and account for complex forms of timing variability that may not be apparent in a simple analysis of mean execution times.
- Process: involves transforming the timing data before statistical testing by mimicking higher-order Differential Power Analysis (DPA) attacks. A common method is the centered product
- example [$t_1$,$t_2$,$t_3$,$t_4$,$t_5$]=[100,105,95,110,90], $\overline{t}$=(100+105+95+110+90)/5=100 cycles.
- Centered Product:
- ($t_1$-$\overline{t}$)*($t_{2}$-$\overline{t}$)=0×5=0
- ($t_1$-$\overline{t}$)*($t_{3}$-$\overline{t}$)=0×−5=0
- ($t_1$-$\overline{t}$)*($t_{4}$-$\overline{t}$)=0×10=0
- ($t_1$-$\overline{t}$)*($t_{5}$-$\overline{t}$)=0×−10=0
- ($t_2$-$\overline{t}$)*($t_{3}$-$\overline{t}$)=5×−5=−25
- ... And so on for all unique pairs.
- Analysis: 5×10=50 are higher than others, suggesting that certain combinations of inputs have a more pronounced effect on execution time.
### Step 3: Apply Statistical Test
- A statistical test, Welch's t-test, is used to determine if the execution time distributions of the two input classes are significantly different.
</s>
:::danger
以你的認知重新書寫,不要只是「畫重點」,後者是你中學時期的技能,現在應該要更上一層樓。
:::
### 實作探討
#### Percentiles in [dudect.h](https://github.com/oreparaz/dudect/blob/master/src/dudect.h)
- Sorting the execution time measurements in ascending order.
```C
qsort(ctx->exec_times, ctx->config->number_measurements, sizeof(int64_t), (int (*)(const void *, const void *))cmp);
```
- Defining several percentile values to determine different thresholds for cropping.
```C
for (size_t i = 0; i < DUDECT_NUMBER_PERCENTILES; i++) {
ctx->percentiles[i] = percentile(
ctx->exec_times, 1 - (pow(0.5, 10 * (double)(i + 1) / DUDECT_NUMBER_PERCENTILES)),
ctx->config->number_measurements);
}
```
The prepare_percentiles function computes these threshold values based on the sorted execution times and stores them in the percentiles array.
#### Abandon the First Batch of Measurements
- Warm-up Phase
- Establishing Baselines
```c
dudect_state_t dudect_main(dudect_ctx_t *ctx) {
prepare_inputs(ctx->config, ctx->input_data, ctx->classes);
measure(ctx);
bool first_time = ctx->percentiles[DUDECT_NUMBER_PERCENTILES - 1] == 0;
dudect_state_t ret = DUDECT_NO_LEAKAGE_EVIDENCE_YET;
if (first_time) {
// throw away the first batch of measurements.
// this helps warming things up.
prepare_percentiles(ctx);
} else {
update_statistics(ctx);
ret = report(ctx);
}
return ret;
}
```
- Flow: with `DUDECT_NUMBER_PERCENTILES` set to 100
- Execution Times Measured: [101, 102, 103, ... 199, 200] cycles.
- `ctx->percentiles[99]` (the last element, given zero-based indexing) is 0.
- `prepare_percentiles(ctx);` is called to calculate and store the percentile thresholds based on the current batch of execution times.
- The 1st percentile might be close to 101 cycles.
- The 50th percentile (median) might be close to 150 cycles.
- The 100th percentile would be 200 cycles.
- These thresholds are stored in the percentiles array
### Compare dudect in lab0
#### `dudect/fixture.c` in lab0 dosen't use Cropping
在 `t_context_t` 結構中新增 `percentiles`
```diff
static bool doit(int mode)
{
...
prepare_inputs(input_data, classes);
bool ret = measure(before_ticks, after_ticks, input_data, mode);
differentiate(exec_times, before_ticks, after_ticks);
+ if (first_time) {
+ prepare_percentiles(t, exec_times);
+ } else {
+ update_statistics(exec_times, classes);
+ ret &= report();
+ }
- update_statistics(exec_times, classes);
- ret &= report();
return ret;
...
}
```
在 `update_statistics` 利用 percentiles 進行 cropping。
```diff
static void update_statistics(const int64_t *exec_times, uint8_t *classes)
{
for (size_t i = 10; /* discard the first few measurements */ i < N_MEASURES;
i++) {
int64_t difference = exec_times[i];
/* CPU cycle counter overflowed or dropped measurement */
if (difference <= 0)
continue;
+ for (size_t crop_index = 0; crop_index < +DUDECT_NUMBER_PERCENTILES;
+ crop_index++) {
+ if (difference < t->percentiles[crop_index]) {
+ t_push(t, difference, classes[i]);
+ }
+ }
}
}
```
根據 [dudect.h](https://github.com/oreparaz/dudect/blob/master/src/dudect.h) 完成了對應的 prepare_percentile 實作並成功通過 traces/trace-17-complexity.cmd 測試
----------------------------------
## 整合 tic-tac-toe 遊戲功能
### Basic setups
1. 創建 [hlist.h](https://github.com/williamlin0518/lab0-c/blob/master/hlist.h)
2. 修改 qtest 程式,使其新增 ttt 命令
```c
ADD_COMMAND(ttt, "Do tic-tac-toe game in console", "");
```
### Monte Carlo Search Tree
MCTS is an algorithm used for making decisions in games, using random simulations to generate statistical data about which moves are most likely to lead to a victory
**most importaint**: balances exploration of untried moves with exploitation of moves known to be effective.
#### Key Components of MCTS
* Selection: find a leaf node by selection policy to balance exploration and exploitation
* Expansion: Once a leaf node is reached, one or more child nodes are added to expand the tree
* Simulation: a simulation (also called a rollout) is performed from the node's game state until a terminal state is reached
* Backpropagation: The result of the simulation is then propagated back up the tree, updating the statistics
#### Monte Carlo Search Tree Implementation
> [commit 1642dbd](https://github.com/williamlin0518/lab0-c/commit/1642dbd9e47bfe111d7073bf78acefb4ea24fd94)
### Fixed-point operation with MCTS
:::danger
詳閱作業規範
:::