## Linux 核心專題: 重做 lab0
> 執行人: ChenFuhuangKye
[解說影片連結](https://www.youtube.com/watch?v=DKD8cJdd8eU&ab_channel=chenfu)
### Reviewed by `LindaTing0106`
> 透過 Valgrind,可以觀察程式運作時的 cache miss 狀況,分析其原因,並改善程式碼。
有針對使 cache miss 降低的議題對程式碼進行改善了嗎?
### Reviewed by `kevinzxc1217`
> 而 list_del 需要多次讀取 node 前後的節點位置,因此容易發生 cache miss
這裡為什麼讀取 node 前後節點位置會容易發生 cache miss 呢?
### Reviewed by `chloe0919`
1. 可以不需要貼上完整程式碼,貼上特別要說明的段落即可。
2. hackmd 撰寫請符合中文和英文字元之間要有空白字元
3.
> 由於每次檢驗彼此獨立,因此 perf 計算出的 cache missing 數據,除非兩種演算法的結果差異達到一定程度,否則無法用於評估演算法的好壞。
可以再清楚說明為何每次檢驗彼此獨立,會影響 perf 計算出的 chche missing 在評估演算法上的效能。
### Reviewed by `Lccgth`
>我們可以發現,merge_sort 發生了 13,331,089 次 cache miss,這是因為遞迴呼叫容易多次出現 cache miss。
可以說明為什麼遞迴呼叫容易多次出現 cache miss。
遞迴呼叫中,每次的調用會存取不同的記憶體位置,可能會導致 cache miss。
### Reviewed by `mesohandsome`
> 重複執行 5 次,分別得到
執行 5 次是否太少?
### Reviewed by `dockyu`
>如果 $p$ 小於 0.05 ,拒絕虛無假設,硬幣為不公平硬幣
>1. $\chi^2 = 36$ ,$p < 0.05$ , 1不符合虛無假設,硬幣為不公平硬幣
>2. $\chi^2 = 1$ , $0.50 < p < 0.25$ , $p > 0.05$ , 符合虛無假設,硬幣為公平硬幣
>3. $\chi^2 = 0$ , $p = 0.99 > 0.05$ , 符合虛無假設,硬幣為公平硬幣
可以說明 $p$ 是如何得出的。
> 首先確認自由度,由於硬幣只有正反面,假設正面出現機率為 x ,反面出現的機率為 1 - x ,由此可以知道拋硬幣的自由度為 1 。接著利用卡方值查表,舉第 1 組為例,此時的卡方值為 36 、自由度為 1 ,而當自由度為 1 且 p = 0.05 時,卡方值為 3.84 ,可以發現第一組的卡方值大於 p = 0.05 時,因此硬幣為不公平硬幣。
### Reviewed by `hugo0406`
能補上觀察圖示「以數學分析來觀察 shuffle 1M 次的結果」的文字敘述嗎?
### Reviewed by `lintin528`
可以嘗試設計幾個資料集來測試不同的排序演算法之表現,又或是他們出現 `worst case` 或 `best case` 的情形並比較。
### Reviewed by `randyuncle`
最後的用 valgrind 觀察 cache missing 的部份不需用把整個包含警告訊息的結果貼上來,可以只貼出關鍵的數據就好,方便閱讀。
## 任務簡介
重做 lab0,並強化統計相關議題。
## TODO: 依據第一次作業規範,重作 lab0
> 善用 git rebase 操作,在最新的 [sysprog21/lab0-c](https://github.com/sysprog21/lab0-c) 開發。
> 可適度彙整其他學員的成果,但務必指明出處並確認其內容有效
### 以數學分析來觀察 shuffle 1M 次的結果
![Figure_1](https://hackmd.io/_uploads/HkyhBKqL0.png)
:::danger
改進上圖的 x 軸標示
> 已更新
:::
```
Expectation: 41666
Observation: {'1234': 41392, '1243': 41907, '1324': 41598, '1342': 41563, '1423': 42164, '1432': 41460, '2134': 41834, '2143': 41677, '2314': 41740, '2341': 41727, '2413': 41207, '2431': 41311, '3124': 41722, '3142': 41696, '3214': 41826, '3241': 41624, '3412': 41788, '3421': 41507, '4123': 41514, '4132': 41613, '4213': 41921, '4231': 41857, '4312': 41867, '4321': 41485}
chi square sum: 26.045792732683726
```
:::danger
說好的數學呢?
列出 Lemma 和 Theorem,並詳盡討論。翻出久違的教科書。
:::
可以透過卡方檢驗比較期望出現頻率分佈是否符合預期的分佈。
### 卡方檢驗方法
計算卡方值 $\chi^2$ ,檢驗卡方值的大小。
卡方值的公式如下
$\chi^2=\sum^k_{i=1}\cfrac{(O_i-E_i)^2}{E_i}$
* $O_i$ 為類別觀察的次數
* $E_i$ 為類別期望的次數
* k 為類別的數量
* 自由度為 df = k-1
舉拋 100 次公平硬幣為例,我們期望會有 50 次正面、 50 次反面。
舉三個例子
1. 如果觀察到有 80 次正面、 20 次反面
* $\chi^2=\cfrac{(80-50)^2}{50} + \cfrac{(20-50)^2}{50} = 36$
2. 如果觀察到有 55 次正面、45 次反面
* $\chi^2=\cfrac{(55-50)^2}{50} + \cfrac{(45-50)^2}{50} = 1$
3. 如果觀察到有 50 次正面、 50 次反面
* $\chi^2=\cfrac{(50-50)^2}{50} + \cfrac{(50-50)^2}{50} = 0$
可以發現當 $\chi^2$ 值越小,觀察次數與期望次數越接近,反之當 $\chi^2$ 值越大,觀察次數與期望次數差異越大,越不符合期望。
由於拋 100 次公平硬幣中,只有正面跟反面兩個類別,因此自由度為 1 。 現在有了卡方值以及自由度,可以透過查表來判斷是否為公平硬幣。
以上面三個例子為例,
* 虛無假設 H0: 硬幣為公平硬幣
* 對立假設 H1: 硬幣為不公平硬幣
如果 $p$ 小於 0.05 ,拒絕虛無假設,硬幣為不公平硬幣
1. $\chi^2 = 36$ ,$p < 0.05$ , 1不符合虛無假設,硬幣為不公平硬幣
2. $\chi^2 = 1$ , $0.50 < p < 0.25$ , $p > 0.05$ , 符合虛無假設,硬幣為公平硬幣
2. $\chi^2 = 0$ , $p = 0.99 > 0.05$ , 符合虛無假設,硬幣為公平硬幣
![image](https://hackmd.io/_uploads/HySvD7bDR.png)
:::danger
修正圖片的存取權限
> 已修正
:::
如果 shuffle 是公平的代表發牌結果出現頻率要一樣,以發了一百萬次牌為例,會有 24 種狀況,每種狀況出現的期望次數為 41666 次。
可以透過卡方檢驗比較期望出现頻率分佈是否符合預期的分佈。如果洗牌是公平的,代表發牌结果出現頻率應當相同。以發了一百萬次牌為例,會有24種情况,每種情況出現的期望次數為 41666 次。
配適度檢定建立的假設如下:
* 虛無假設 H0: 數據符合出現的頻率分佈
* 對立假設 H1: 數據不符合出現的頻率分佈
計算的 p-value 為 0.299 大於 0.005 ,無法拒絕虛無假設,因此結論 shuffle 的結果符合期望的結果。
```shell
$ python test_shuffle.py
Power_divergenceResult(statistic=np.float64(26.045119999999997),
pvalue=np.float64(0.29874079973512085))
```
```python
import numpy as np
from scipy import stats
f_obs = [41392, 41907, 41598, 41563, 42164, 41460, 41834, 41677, 41740, 41727, 41207, 41311,
41722, 41696, 41826, 41624, 41788, 41507, 41514, 41613, 41921, 41857, 41867, 41485]
total_obs = sum(f_obs)
f_exp = [total_obs / len(f_obs)] * len(f_obs)
output = stats.chisquare(f_obs=f_obs, f_exp=f_exp)
print(output)
```
### 研讀 Linux 核心原始程式碼的 [lib/list_sort.c](https://github.com/torvalds/linux/blob/master/lib/list_sort.c)
list_sort 參數的意義。
* `priv` : `list_sort` 函式不會去使用,而是傳遞給 `cmp` 比較函式,可以用於傳遞需要在比較函式中使用的額外訊息。
* `head` : 為鏈結串列的開頭。
* `cmp` : 為一個比較函式,用於比較兩個元素之間的排列順序,並回傳一個整數來表示兩個元素的比較結果。
由於 `head` 跟 `cmp` 不能為 NULL ,加上 `__attribute__((nonnull(2, 3)))` 讓編譯器可以發出警告。
```c
__attribute__((nonnull(2, 3))) void list_sort(void *priv,
struct list_head *head,
list_cmp_func_t cmp)
```
list_sort 初始值
* `list` : 這是一個指向鏈結串列中第一個節點的指標,並確保此鏈結串列數量不是 0 或是 1 。
* `pending` : 這是一個指向待處理節點的指標,其初始值為 NULL 。
* `count` : 這是一個整數值,用來紀錄待處理節點的數量。
:::danger
注意用語,務必詳閱 https://hackmd.io/@sysprog/it-vocabulary
:::
list_sort 有分成三個區塊。
1. 逐一走訪每個節點,並把節點逐一加入 `pending` , 當 `pending` 的數量 + 1 後為 2 的冪就不合併,反之合併。
2. 當 `list` 中沒有節點時,將 `pending` 中所有節點合併。
3. 最後整理成環狀串列。
#### 執行步驟
##### `在 do while` 之前
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
head [label="head", style="bold"]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
head -> node6
node6 -> node5
node5 -> node4
node4 -> node3
node3 -> node2
node2 -> node1
list -> node6:n
}
```
##### count = 1
加入 6 到 `pending`
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5 -> node4
node4 -> node3
node3 -> node2
node2 -> node1
list -> node5:w
pending -> node6:w
}
```
##### count = 2
加入 5 到 `pending`
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5 -> node6
node4 -> node3
node3 -> node2
node2 -> node1
list -> node4:w
pending -> node5:w
}
```
##### count = 3
加入 4 到 `pending`
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5 -> node6
node4 -> node5
node3 -> node2
node2 -> node1
list -> node3:w
pending -> node4:w
}
```
##### count = 4
加入 3 到 `pending` ,5 節點的分支與 6 節點的分支合併
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5:s -> node6:n
node4 -> node5
node3 -> node4
node2 -> node1
list -> node2:w
pending -> node3:w
}
```
##### count = 5
加入 2 到 `pending` , 3 節點的分支與 4 節點的分支合併。
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5:s -> node6:n
node3 -> node5
node3:s -> node4:n
node2 -> node3
list -> node1:w
pending -> node2:w
}
```
##### count = 6
加入 1 到 `pending` , 3 節點的分支與 5 節點的分支合併。
```graphviz
digraph ele_list {
rankdir=LR
node[shape=record]
node6 [label="6", style="bold"]
node5 [label="5", style="bold"]
node4 [label="4", style="bold"]
node3 [label="3", style="bold"]
node2 [label="2", style="bold"]
node1 [label="1", style="bold"]
list [label="list", style="normal", color="white"]
pending [label="pending", style="normal", color="white"]
node5:s -> node6:n
node4:s -> node5:n
node3:s -> node4:n
node2 -> node3
node1 -> node2
list
pending -> node1:w
}
```
##### 當 `list` 沒有節點
將 1 節點的分支、 2 節點的分支與 3 節點的分支合併。
#### 最後將頭尾接上
## 強化統計的基礎
> 例如針對排序和其分析,需要多大的樣本空間?
## TODO: 改進鏈結串列的排序
> 拉近與 Linux 核心鏈結串列排序的距離,甚至在特定狀況得以超越其性能。
## TODO: 善用工具進行分析
> 如 perf, valgrind, massif
除了比較執行時間外,判斷排序演算法的好壞還可以觀察其發生 cache missing 的情況。由於 CPU 與 cache 之間的溝通速度遠快於與記憶體的溝通速度,如果能夠降低 cache missing 的次數,則代表排序演算法較優。然而,每次計算 cache missing 的結果都可能有所不同,因此找到適當的樣本大小來代表演算法的 cache missing 是非常重要的。
### merge_sort
實作 merge_sort 可以利用 `Divide and Conquer` 使用遞迴方法,每次遞迴先將 list 分成兩個左右兩個區塊,直到 list 不能分割,接著合併左右 list 直到全部合併。
```c
void merge_sort(struct list_head *head, bool descend)
{
if (!head || list_empty(head) || list_is_singular(head))
return;
struct list_head *mid = head;
for (struct list_head *fast = head->next;
fast != head && fast->next != head; fast = fast->next->next)
mid = mid->next;
LIST_HEAD(left);
LIST_HEAD(right);
list_cut_position(&left, head, mid);
list_splice_init(head, &right);
merge_sort(&left, descend);
merge_sort(&right, descend);
merge_list(head, &left, &right, descend);
}
void merge_list(struct list_head *head,
struct list_head *left,
struct list_head *right,
bool descend)
{
LIST_HEAD(tmp);
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);
if (descend ? strcmp(left_entry->value, right_entry->value) > 0
: strcmp(left_entry->value, right_entry->value) < 0) {
list_move_tail(left->next, &tmp);
} else {
list_move_tail(right->next, &tmp);
}
}
list_splice_tail_init(list_empty(left) ? right : left, &tmp);
list_splice_init(&tmp, head);
}
```
### 使用 perf 計算資料排序程式的 cache missing
首先,利用 `lscpu` 得知測試環境的 L3 cache 大小為 12MB,`list_head` 大小為 16B 。可以將樣本大小設為 12 * 1024 * 64。我設計了一個測試程式,並測試。
```c
#include <stdio.h>
#include <stdlib.h>
#include "queue.h"
#define N 12 * 1024 * 64
int main() {
struct list_head *head = q_new();
for(int i = 0; i < N; i++)
q_insert_head(head, "world");
q_sort(head, 0);
return 0;
}
```
```shell
$ 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): 12
On-line CPU(s) list: 0-11
Vendor ID: GenuineIntel
Model name: Intel(R) Core(TM) i7-10750H CPU @ 2.60GHz
CPU family: 6
Model: 165
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
Stepping: 2
CPU max MHz: 5000.0000
CPU min MHz: 800.0000
BogoMIPS: 5199.98
```
```shell
$ sudo perf stat -e cache-references,cache-misses ./merge_sort
Performance counter stats for './merge_sort':
59,127,873 cache-references
23,874,713 cache-misses # 40.38% of all cache refs
0.377605633 seconds time elapsed
0.351518000 seconds user
0.023967000 seconds sys
```
重複執行 5 次,分別得到
:::danger
為何重複 5 次呢?而不是其他次數?你的統計學素養在哪?
:::
| | 第一次取樣 | 第二次取樣 | 第三次取樣 | 第四次取樣 | 第五次取樣 |
|:----------------:|:----------:|:----------:|:----------:|:----------:|:----------:|
| cache-references | 59127873 | 57615096 | 57638423 | 57674422 | 59593397 |
| cache-misses | 23874713 | 24929428 | 24034141 | 24130806 | 24184553 |
如何確保這些分佈是否都相同可以利用卡方檢定 (Chi-Squared Test) 中的同質性檢定 ([Test of Homogeneity](https://courses.lumenlearning.com/wm-concepts-statistics/chapter/test-of-homogeneity/)) ,檢驗對於同一個類別變數,資料內不同群體出現的頻率分佈是否相同。
同質性檢定建立的假設如下:
* 虛無假設 H0: 對於 cache missing 類別,不同的子群體之間存在**相同**的分佈。
* 對立假設 H1: 對於 cache missing 類別,不同的子群體之間存在**不同**的分佈。
卡方檢定中需要找出觀察與預期,我們需要定義出預期,這邊我們的虛無假設是『對於 cache missing 類別,不同的子群體之間存在**相同**的分佈。』,因此我們的同質性預期可以用 cache 狀況代表。
* cache-references ,總共有 291,649,211
* cache-misses ,總共有 121,153,641
* 預期的 cache missing 比例為 121,153,641 / 291,649,211 = 41.51%
利用 python 程式計算同質性檢定
```python
import numpy as np
from scipy import stats
output = stats.chi2_contingency(
observed = np.array([
[35253160 ,32685668 ,33604282 ,33543616 ,35408844],
[23874713 ,24929428 ,24034141 ,24130806 ,24184553]
])
)
print(output)
```
```
$ python main.py
Chi2ContingencyResult(statistic=np.float64(129007.8621825518), pvalue=np.float64(0.0), dof=4, expected_freq=array([[34565635.80424913, 33681279.64222307, 33694916.40176633,
33715961.10133328, 34837777.0504282 ],
[24562237.19575087, 23933816.35777693, 23943506.59823367,
23958460.89866673, 24755619.94957181]]))
```
可以得到
* Statistic ($x^2$) : 129007.8621825518
* 值越大代表觀察頻率與期望頻率差異顯著
* p-value: 0.0
* 值為 0小於0.05 ,在同質性檢定的結論,不是每次的 cache missing 都一樣
因此, 本次的同質性檢定得出各組數據關係不相同,而是互相獨立。雖然說可以大致推測出 cache missing 比例為 41.51% ,然而經過卡方檢驗後,可以得知每次取樣的結果彼此互相獨立。有可能與測試程式有關,因為計算 cache missing 也有包含 list 在增加 node 的過程。
#### 增加採樣次數
我寫了一個腳本,進行了 10000 次採樣,其 p-value 也接近於 0 。我好奇這些資料的分佈狀況,於是以 1% 為單位累加出現的次數,並畫出了cache miss機率分佈圖。結果顯示,cache missing 大致呈現常態分佈,但誤差範圍很大,這與同質性檢驗的結果一致,且本次的執行結果與之前 cache missing 結果不一樣。由於每次檢驗彼此獨立,因此 perf 計算出的 cache missing 數據,除非兩種演算法的結果差異達到一定程度,否則無法用於評估演算法的好壞。
![Figure_1](https://hackmd.io/_uploads/Hy_e5DtUC.png)
```bash
#!/bin/bash
# Initialize result arrays
cache_references_results=()
cache_misses_results=()
cache_correct_results=()
# Repeat 10000 times
for i in {1..10000}
do
# Run perf command and store the output in a variable
output=$(perf stat -e cache-references,cache-misses ./merge_sort 2>&1)
# Extract cache-references and cache-misses values from the output
cache_references=$(echo "$output" | grep 'cache-references' | awk '{print $1}' | tr -d ',')
cache_misses=$(echo "$output" | grep 'cache-misses' | awk '{print $1}' | tr -d ',')
# Calculate cache_correct
cache_correct=$((cache_references - cache_misses))
# Store the results in arrays
cache_references_results+=("$cache_references")
cache_misses_results+=("$cache_misses")
cache_correct_results+=("$cache_correct")
cache_miss_rate=$(awk "BEGIN {print ($cache_misses/$cache_references)*100}")
# Print the extracted values for the current iteration
echo "Run $i:"
echo "Cache References: $cache_references"
echo "Cache Misses: $cache_misses"
echo "Cache Correct: $cache_correct"
echo "Cache missing: $cache_miss_rate%"
echo "---------------------------------"
done
# Write all cache references results to a file
echo "${cache_references_results[@]}" > cache_references.txt
# Write all cache misses results to a file
echo "${cache_misses_results[@]}" > cache_misses.txt
# Write all cache correct results to a file
echo "${cache_correct_results[@]}" > cache_correct.txt
# Print all results
echo "All Cache References Results: ${cache_references_results[@]}"
echo "All Cache Misses Results: ${cache_misses_results[@]}"
echo "All Cache Correct Results: ${cache_correct_results[@]}"
```
### 使用 valgrind 觀察 cache misssing
valgrind 可以觀察到程式運作時 cache missing 發生的狀況,可以找出是那一行程式發生的多次的 cache missing 。
```
$valgrind --tool=cachegrind ./merge_sort
==558249== ./.valgrindrc was not read as it is either not a regular file,
==558249== or is world writeable, or is not owned by the current user.
==558249== Cachegrind, a cache and branch-prediction profiler
==558249== Copyright (C) 2002-2017, and GNU GPL'd, by Nicholas Nethercote et al.
==558249== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==558249== Command: ./merge_sort
==558249==
--558249-- warning: L3 cache found, using its data for the LL simulation.
--558249-- warning: specified LL cache: line_size 64 assoc 16 total_size 12,582,912
--558249-- warning: simulated LL cache: line_size 64 assoc 24 total_size 12,582,912
==558249== brk segment overflow in thread #1: can't grow to 0x485b000
==558249== (see section Limitations in user manual)
==558249== NOTE: further instances of this message will not be shown
==558249==
==558249== I refs: 2,264,282,423
==558249== I1 misses: 1,334
==558249== LLi misses: 1,326
==558249== I1 miss rate: 0.00%
==558249== LLi miss rate: 0.00%
==558249==
==558249== D refs: 1,348,111,526 (871,215,763 rd + 476,895,763 wr)
==558249== D1 misses: 24,598,017 ( 19,308,724 rd + 5,289,293 wr)
==558249== LLd misses: 7,576,318 ( 4,952,807 rd + 2,623,511 wr)
==558249== D1 miss rate: 1.8% ( 2.2% + 1.1% )
==558249== LLd miss rate: 0.6% ( 0.6% + 0.6% )
==558249==
==558249== LL refs: 24,599,351 ( 19,310,058 rd + 5,289,293 wr)
==558249== LL misses: 7,577,644 ( 4,954,133 rd + 2,623,511 wr)
==558249== LL miss rate: 0.2% ( 0.2% + 0.6% )
$ sudo cg_annotate cachegrind.out.558249
--------------------------------------------------------------------------------
I1 cache: 32768 B, 64 B, 8-way associative
D1 cache: 32768 B, 64 B, 8-way associative
LL cache: 12582912 B, 64 B, 24-way associative
Command: ./merge_sort
Data file: cachegrind.out.558249
Events recorded: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Events shown: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Event sort order: Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
Thresholds: 0.1 100 100 100 100 100 100 100 100
Include dirs:
User annotated:
Auto-annotation: on
--------------------------------------------------------------------------------
Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
--------------------------------------------------------------------------------
2,264,282,423 (100.0%) 1,334 (100.0%) 1,326 (100.0%) 871,215,763 (100.0%) 19,308,724 (100.0%) 4,952,807 (100.0%) 476,895,763 (100.0%) 5,289,293 (100.0%) 2,623,511 (100.0%) PROGRAM TOTALS
--------------------------------------------------------------------------------
Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw file:function
--------------------------------------------------------------------------------
352,321,488 (15.56%) 6 ( 0.45%) 6 ( 0.45%) 125,829,110 (14.44%) 4,121 ( 0.02%) 14 ( 0.00%) 73,138,163 (15.34%) 0 0 ???:merge_list
243,793,840 (10.77%) 1 ( 0.07%) 1 ( 0.08%) 121,896,920 (13.99%) 1,365 ( 0.01%) 5 ( 0.00%) 48,758,768 (10.22%) 10,090 ( 0.19%) 74 ( 0.00%) ???:list_empty
221,769,976 ( 9.79%) 29 ( 2.17%) 29 ( 2.19%) 37,750,909 ( 4.33%) 0 0 37,746,111 ( 7.91%) 1,572,303 (29.73%) 1,571,760 (59.91%) ./malloc/./malloc/malloc.c:_int_malloc
186,122,168 ( 8.22%) 5 ( 0.37%) 5 ( 0.38%) 98,828,268 (11.34%) 13,331,089 (69.04%) 3,119,860 (62.99%) 33,292,271 ( 6.98%) 1,365 ( 0.03%) 9 ( 0.00%) ???:merge_sort
173,958,096 ( 7.68%) 4 ( 0.30%) 4 ( 0.30%) 39,321,600 ( 4.51%) 4,919,860 (25.48%) 1,622,063 (32.75%) 0 0 0 ./string/../sysdeps/x86_64/multiarch/strcmp-avx2.S:__strcmp_avx2
173,015,040 ( 7.64%) 0 0 94,371,840 (10.83%) 0 0 62,914,560 (13.19%) 0 0 ???:list_add_tail
141,557,760 ( 6.25%) 1 ( 0.07%) 1 ( 0.08%) 78,643,200 ( 9.03%) 1,034,147 ( 5.36%) 209,220 ( 4.22%) 47,185,920 ( 9.89%) 3,277,277 (61.96%) 655,694 (24.99%) ???:list_del
125,829,120 ( 5.56%) 1 ( 0.07%) 1 ( 0.08%) 39,321,600 ( 4.51%) 0 0 39,321,600 ( 8.25%) 0 0 ???:list_move_tail
89,653,302 ( 3.96%) 6 ( 0.45%) 6 ( 0.45%) 33,030,163 ( 3.79%) 1 ( 0.00%) 0 25,165,839 ( 5.28%) 196,159 ( 3.71%) 196,156 ( 7.48%) ???:test_malloc
69,205,812 ( 3.06%) 7 ( 0.52%) 7 ( 0.53%) 17,301,518 ( 1.99%) 1 ( 0.00%) 1 ( 0.00%) 6,291,973 ( 1.32%) 0 0 ./malloc/./malloc/malloc.c:malloc
53,477,308 ( 2.36%) 2 ( 0.15%) 2 ( 0.15%) 26,738,654 ( 3.07%) 4,095 ( 0.02%) 15 ( 0.00%) 17,301,482 ( 3.63%) 10,770 ( 0.20%) 73 ( 0.00%) ???:list_splice
50,128,732 ( 2.21%) 3 ( 0.22%) 3 ( 0.23%) 12,582,920 ( 1.44%) 4 ( 0.00%) 2 ( 0.00%) 4,718,595 ( 0.99%) 0 0 ./stdlib/./stdlib/random_r.c:random_r
33,030,165 ( 1.46%) 3 ( 0.22%) 3 ( 0.23%) 12,582,920 ( 1.44%) 1 ( 0.00%) 1 ( 0.00%) 3,145,730 ( 0.66%) 0 0 ./stdlib/./stdlib/random.c:random
32,243,671 ( 1.42%) 4 ( 0.30%) 4 ( 0.30%) 17,301,482 ( 1.99%) 3,699 ( 0.02%) 17 ( 0.00%) 9,437,172 ( 1.98%) 212,607 ( 4.02%) 197,017 ( 7.51%) ???:list_cut_position
29,884,435 ( 1.32%) 3 ( 0.22%) 3 ( 0.23%) 9,437,190 ( 1.08%) 1 ( 0.00%) 1 ( 0.00%) 4,718,595 ( 0.99%) 0 0 ???:fail_allocation
29,097,966 ( 1.29%) 1 ( 0.07%) 1 ( 0.08%) 11,010,041 ( 1.26%) 0 0 4,718,589 ( 0.99%) 0 0 ???:list_is_singular
28,311,744 ( 1.25%) 31 ( 2.32%) 30 ( 2.26%) 14,155,838 ( 1.62%) 1,049 ( 0.01%) 13 ( 0.00%) 20 ( 0.00%) 1 ( 0.00%) 1 ( 0.00%) ???:???
28,311,528 ( 1.25%) 2 ( 0.15%) 2 ( 0.15%) 14,155,764 ( 1.62%) 0 0 9,437,176 ( 1.98%) 0 0 ???:INIT_LIST_HEAD
26,738,654 ( 1.18%) 1 ( 0.07%) 1 ( 0.08%) 13,369,327 ( 1.53%) 0 0 8,650,741 ( 1.81%) 4,049 ( 0.08%) 12 ( 0.00%) ???:list_splice_tail
25,952,256 ( 1.15%) 3 ( 0.22%) 3 ( 0.23%) 8,650,752 ( 0.99%) 0 0 7,077,888 ( 1.48%) 0 0 ???:q_insert_head
25,165,792 ( 1.11%) 1 ( 0.07%) 1 ( 0.08%) 7,864,310 ( 0.90%) 0 0 7,864,310 ( 1.65%) 0 0 ???:list_splice_init
22,806,540 ( 1.01%) 3 ( 0.22%) 3 ( 0.23%) 1,572,865 ( 0.18%) 0 0 3,145,730 ( 0.66%) 0 0 ./string/../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S:__memset_avx2_unaligned_erms
20,447,245 ( 0.90%) 0 0 9,437,190 ( 1.08%) 0 0 4,718,595 ( 0.99%) 0 0 ???:find_footer
18,874,368 ( 0.83%) 2 ( 0.15%) 2 ( 0.15%) 6,291,456 ( 0.72%) 0 0 5,505,024 ( 1.15%) 0 0 ???:test_strdup
17,301,504 ( 0.76%) 1 ( 0.07%) 1 ( 0.08%) 9,437,184 ( 1.08%) 0 0 6,291,456 ( 1.32%) 0 0 ???:list_add
12,582,896 ( 0.56%) 0 0 3,932,155 ( 0.45%) 0 0 3,932,155 ( 0.82%) 0 0 ???:list_splice_tail_init
11,796,480 ( 0.52%) 2 ( 0.15%) 2 ( 0.15%) 2,359,296 ( 0.27%) 0 0 1,572,864 ( 0.33%) 0 0 ./string/../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:__memcpy_avx_unaligned_erms
11,010,048 ( 0.49%) 1 ( 0.07%) 1 ( 0.08%) 1,572,864 ( 0.18%) 1 ( 0.00%) 1 ( 0.00%) 0 0 0 ./string/../sysdeps/x86_64/multiarch/strlen-avx2.S:__strlen_avx2
6,291,474 ( 0.28%) 0 0 2,359,300 ( 0.27%) 0 0 786,437 ( 0.16%) 0 0 ???:main
3,145,219 ( 0.14%) 2 ( 0.15%) 2 ( 0.15%) 0 0 0 1 ( 0.00%) 0 0 ./malloc/./malloc/arena.c:malloc
--------------------------------------------------------------------------------
The following files chosen for auto-annotation could not be found:
--------------------------------------------------------------------------------
./malloc/./malloc/arena.c
./malloc/./malloc/malloc.c
./stdlib/./stdlib/random.c
./stdlib/./stdlib/random_r.c
./string/../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S
./string/../sysdeps/x86_64/multiarch/memset-vec-unaligned-erms.S
./string/../sysdeps/x86_64/multiarch/strcmp-avx2.S
./string/../sysdeps/x86_64/multiarch/strlen-avx2.S
```
我們可以發現,merge_sort 發生了 13,331,089 次 cache miss,這是因為遞迴呼叫容易多次出現 cache miss。同樣地,list_del 出現了 1,034,147 次 cache miss,這是由於 list_move_tail 會呼叫 list_del,而 list_del 需要多次讀取 node 前後的節點位置,因此容易發生 cache miss。透過 Valgrind,可以觀察程式運作時的 cache miss 狀況,分析其原因,並改善程式碼。