contributed by < steven523 >
SuNsHiNe-75
stevendd543
q_free
有提到「q_release_element 刪除 entry 時不會發生錯誤」,具體錯誤原因可以清楚描述。q_ascend
中前段的漢語表達讓讀者不容易,可透過 ChatGPT 修飾。q_new
allocate 翻譯為「配置」,以區格 dispatch (分發/分配) 的翻譯
已更改
使用 malloc
為佇列 new
配置一個動態記憶體空間,如果成功配置足夠大小的空間給 new
指標,則透過 INIT_LIST_HEAD
做初始化使 new
的 prev 和 next 皆指向自己,否則返回 NULL
q_free
留意資訊科技詞彙翻譯
已更改
用 list.h
的巨集 list_for_each_entry_safe
逐一走訪整個佇列並透過 safe
紀錄每個迭代當前節點的下一個節點,以致逐一走訪節點的過程中安全使用 q_release_element
刪除目前的節點
根據 Dictionary.com 的解釋: (作為及物動詞和不及物動詞都有類似的意思,以下列出作為及物動詞的寓意)
其實這意思很好懂,就像我們「走過」/「穿越」校園一般,於是 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) 源於以下二個希臘詞:
最初這是由奧地利物理學家波茲曼 (Ludwig Boltzmann) 於統計力學領域 (statistical mechanics) 提出的概念,其一廣為人知的結果是:在經過長時間後,時間平均將會趨近空間平均。此事實在動力系統扮演極重要的角色,在隨機分析領域亦然。
因此,若貿然將 traverse 翻譯為「遍歷」,一來語意不清,二來增加和理工多項領域專業人員的溝通成本,實在得不償失。
資訊科技詞彙翻譯
說好的進度呢?
q_insert_head
新增一個 element_t
並使用 malloc
為其配置記憶體空間,接著使用 list.h
中的 list_add
將節點插入佇列開頭。但是在測試時出現以下訊息:
這個錯誤提示是在說建立新的佇列元素時需要為其分配記憶體並複製字串
所以我使用 strdup
這個函式來複製參數 s 並配置記憶體空間給它
q_insert_tail
與 q_insert_head
差不多的做法,新增一個 element_t
並使用 malloc
為其配置記憶體空間,接著使用 strdup
來複製參數 s 並配置記憶體空間給它,最後使用 list.h
中的 list_add_tail
將節點插入佇列尾端
q_remove_head
避免非必要的項目縮排 (即 *
, -
或數字),以清晰、明確,且流暢的漢語書寫。
授課教師在大學教書十餘年,見到不少學生在課堂表現不俗,但在面試場合卻無法清晰地闡述自己的成果和獨到的成就,從而錯過躋身一流資訊科技企業的機會。為此,要求學員書寫開發紀錄,並隨時做好「當主管問起,就能清晰解釋自己的投入狀況」,是強調讓學員「翻身」的本課程所在意的事。
濫用條列 (或說 bullet) 來展現,乍看很清爽,但在面試的場合中,就很難用流暢的話語說明。反之,若平日已有這方面的練習,則面試過程中,才可充分展現自己專業之處。
利用 list.h
裡的 list_first_entry
將第一個節點的位址取出,接著使用 list_del_init
移除頭節點後讓節點的前後相連
最後將移除的節點數值複製到 sp ,並且複製字串到 sp 時為其加上 null terminator 的空間
q_remove_tail
同 q_remove_head
,使用 list_last_entry
將最後一個節點的位址取出,並改用 list_del_init(head->next);
移除尾節點
q_size
首先判斷佇列是否為空或不存在,再來用 list.h
中的 list_for_each
逐一走訪佇列中的每個節點並將長度數值 + 1
q_delete_mid
參考第一周教材提到的快慢指標來實作,在每次迴圈中 fast
都會比 indir_slow
移動兩倍的距離,直到 fast
或fast->next
指回 head
,此時 slow
就會剛好在佇列中間的位置
接著使用 list_del_init
確保在移除目標節點後前後節點相連
最後利用 list_entry
找出目標節點並使用 q_release_element
釋放記憶體空間
q_delete_dup
為一個排序後的鏈結串列刪除所有有重複字串的節點
想法是使用 list_for_each_entry_safe
逐一走訪節點,且能得到當前的以及下一個 element_t
的位址
使用 strcmp 比對兩者的 value
是否相同,相同的話則透過 list_del_init
和 q_release_element
來移除並釋放該重複節點的記憶體空間
宣告一個變數 flag
用來確認如果字串相同,則在下一個迴圈就會以相同方法移除另一個重複的節點
避免非必要的項目縮排 (即 *
和 -
),以清晰、明確,且流暢的漢語書寫。
但在做輸入 gdb ./qtest
測試後發現以下錯誤
access 的翻譯是「存取」,而非「訪問」(visit)
已更改
意思是在呼叫 strcmp
函式時存取了無效的記憶體位址,檢查過後發現是當透過 list_for_each_entry_safe
走訪到最後一個節點時,該節點的 next 是指向 head
,但 head
的型態是 list_head
,所以它沒有 value
可以存取,進而發生 sagemetation fault
所以我多加了一個判斷式,判斷當前節點的下一個節點是否指向 head
q_swap
宣告 cur
指標指向 head
,再宣告兩個指標指向第一個節點和第二個節點
在每次迴圈時將指標兩兩交換,並在迴圈結束前將兩個指標皆往後移動兩個節點,直到其中一個指標指向 head
要注意更新鏈結指標的順序,如果更新邏輯有誤可能會導致鏈結結構錯亂,例如重複處理節點或形成循環鏈結
避免非必要的項目縮排 (即 *
和 -
),以清晰、明確,且流暢的漢語書寫。
改進你的漢語表達。
q_reverse
使用 list_for_each
走訪佇列,透過 list_del
將當前的節點移除,接著用 list_add
將其移至開頭,走訪完後即完成反轉,但是在測試報出以下錯誤訊息:
意思是形成了一個無窮迴圈,這個無窮迴圈導致程式運行時間超過了限制
後來想到了原因,首先 list_for_each
巨集如下:
可以發現這個 node
指向的節點在被移至佇列開頭後,進入下個迴圈前仍然指向開頭節點的下一個節點。因此每次迴圈都會做佇列前二節點互相交換的操作,從而導致了一個無限循環。
所以使用 list_for_each_safe
走訪並透過 safe
儲存下個節點,在每次迴圈結束時 node
會指向 safe
,就能透過 node
逐一將節點移至開頭,最後實作佇列反轉的效果
不過後來在 The Linux Kernel API 中找到其實可以只利用 list_move
來實作 list_del
+ list_add
的功能,所以修改函式進一步精簡程式碼
q_reverseK
將每一組的 k 個節點逐一移至最前面實作佇列反轉,接著迭代每一組
使用 q_size
紀錄佇列整體長度,來確保最後剩下不足 k 個節點不會做反轉排列,並透過間接指標 reverse_node
紀錄 cur->next
的位址,tmp
用來紀錄每一組的 head
「當前」一詞可能會造成閱讀的困難。例如下面的「當前組」是「當 前面一組」,抑或「目前這組」呢?
你可回想國中和高中的教材中,「當前」出現次數較高,還是「目前」較高呢?若是後者較頻繁出現,為何你現在不依據中學教材的方式來書寫?
已更改
利用 q_reverse
做法為目前這組實作反轉後 ,此時 cur
會指向該組的最後一個節點,並將 cur->next
指派給 cur
,此時 cur
就會指向下一組的第一個節點
所以下一次迭代就會從 *reverse_node
指向的節點開始逐一移至該組最前面,實作反轉
q_sort
在 trace-15-perf.cmd 的文件裡有提到測試 sort 要求時間複雜度必須為 ,若使用像 bubble sort 這種時間複雜度為 的排序法會造成測試失敗,所以選擇 merge sort 以符合期待。
參考你所不知道的 C 語言: linked list 和非連續記憶體 merge sort 的實作,我使用三個函式呈現
你如何確保現有的測試方式/資料已涵蓋排序演算法的最差狀況?
q_sort
執行 merge_sort 前要先將 head
斷開,使其變成單向鏈結串列來避免產生無窮迴圈
最後要再迭代一次排序後的鏈結串列,因為當前排序過後是單向鏈結串列,所以要將 prev
指標更新,也需將佇列的尾巴與開頭相互連結,讓其變回循環雙向鏈結串列,以維持 list_head
的型態
merge_sort
如果鏈結串列為空或只包含一個節點,則直接返回。
先透過快慢指標的方法來尋找中間節點,再呼叫 merge_list
將第一個節點開頭的佇列及中間節點開頭的佇列,合併成一個有序的佇列。
其中 slow->next = NULL;
是必須要將第一個節點開頭的串列尾巴指向 NULL
。
merge_list
使用 list_entry
可以取得 left
指向的 struct list_head
結構中 element_t
的指標,然後再透過 element_t
裡 value
的值做比較,比較後將 value
較小的放到新的佇列裡,然後將 ptr
移至下一個位置,當迭代完成後,將剩下的節點加入佇列
運用 indirect pointer 的技巧避免佇列合併過程中創建額外節點的記憶體空間
執行結果
q_ascend
題目是說如果目刪除具有較小值在右側的節點,結果為升序的佇列。
一開始的想法是利用從佇列的第一個節點開始比對右側的每個節點,但這個方法會用到雙層迴圈,不過思考後發現可以從佇列尾端比對,只需要單層迴圈即可
方法是先利用 q_reverse
將佇列反轉,以 min
儲存最小值,並用 list_for_each_entry_safe
逐一比對當前節點與 min
的大小,如果比 min
大就刪掉,否則將其指派給 min
最後的結果會是降序的佇列,所以要再用 q_reverse
將其反轉回來
不過在與 ollieni 討論時發現 The Linux Kernel API 裡面的 list_for_each_entry_safe_reverse
可以實作從佇列尾端走訪,但 list.h
裡並沒有看到相關實作,不過可以將 list_for_each_entry_safe
巨集裡面的所有 ->next
改成 ->prev
實作相同功能,以省去我原本還要將佇列做兩次反轉的方法
q_descend
與 q_ascend
做法類似,只差在刪除的是具有較大值在右側的節點
q_merge
從 queue.h
可以了解 queue_contex_t
的架構描述
q
用來指向 queue 的 headchain
用來將每一個 queue_contex_t
結構串連起來size
表示這個 queue 的長度id
表示每一個 queue 唯一的編號接著從 qtest.c
發現有宣告 queue_chain_t
的結構,並從do_new
函式可以發現,他是用來當 queue_contex_t
結構的 head
,並且每次配置一個新的 queue_contex_t
結構的記憶體空間,都會將此結構插入到佇列的尾端
同時在 qtest.c
的 do_merge
函式可以發現它傳入的參數是 queue_chain_t
結構的 head,因為 qtest.c
裡面有宣告 q_chain_t
的結構,所以邊界條件不用判斷 !head
,所以設置 list_empty(head)
與 list_is_singular(head)
為邊界條件即可
想法就是逐一將所有佇列合併到第一個佇列,與 q_sort
差不多,執行 merge_list
前要先將 head
斷開,使其變成單向鏈結串列來避免產生無窮迴圈,最後要再迭代一次排序後的佇列,讓他變回循環雙向鏈結串列
其中要加上 INIT_LIST_HEAD(pos->q)
將原本的串列做初始化
qtest
提供新的命令 shuffle
參考 Fisher–Yates shuffle 演算法,對佇列中所有節點進行 shuffle,做法是從 1 到 n 之間隨機出一個數字和最後一個數 n 交換,然後從 1 到 n-1 之間隨機出一個數和倒數第二個數 n-1 交換..
假設有一長度為 的陣列,其洗牌演算法的條件:
此為提供的 pseudocode
在 queue.c
按照上述程式碼實作 q_shuffle
迴圈從佇列最後一個元素開始,每次迭代皆會隨機挑一個數並以 cur
指標紀錄節點位置,接著將其數值與 end
的數值做交換
end
會透過 end = end->prev
紀錄每次迭代最後一個節點的位置。
交換節點數值的方法透過以下 swap
函式來執行
接著在 qtest.c
裡的 console_init
中加上以下新命令,並寫一個 do_shuffle
呼叫我們實作的 q_shuffle
不過在我編譯時出現以下錯誤:
意思是編譯器在 do_shuffle
中找不到 q_shuffle
函式的聲明,並將其視為一個隱式函式聲明,所以導致編譯錯誤
file 的翻譯是「檔案」,而非「文件」(document)
已更改
後來我在 qtest.c
檔案中添加 void q_shuffle(struct list_head *head);
,這樣編譯器就可以找到這個函式的聲明,並正確地編譯 do_shuffle
根據作業說明:統計方法驗證 shuffle
將測試程式新增到 scripts/shuffle.py
執行 $ python3 scripts/shuffle.py
在測試 shuffle 1000000 次後,24種結果各自的頻率如下表:
排列組合 | 觀察到的頻率 | 期待的頻率 | |
---|---|---|---|
[1, 2, 3, 4] | 41,432 | 41,666 | 1.31416502664 |
[1, 2, 4, 3] | 41,588 | 41,666 | 0.14601833629 |
[1, 3, 2, 4] | 41,972 | 41,666 | 2.24729995680 |
[1, 3, 4, 2] | 41,910 | 41,666 | 1.42888686219 |
[1, 4, 2, 3] | 41,562 | 41,666 | 0.25958815341 |
[1, 4, 3, 2] | 41,637 | 41,666 | 0.02018432294 |
[2, 1, 3, 4] | 41,607 | 41,666 | 0.08354533672 |
[2, 1, 4, 3] | 41,544 | 41,666 | 0.35722171554 |
[2, 3, 1, 4] | 41,860 | 41,666 | 0.90327845245 |
[2, 3, 4, 1] | 41,403 | 41,666 | 1.66008256132 |
[2, 4, 1, 3] | 41,900 | 41,666 | 1.31416502664 |
[2, 4, 3, 1] | 41,338 | 41,666 | 2.58205731292 |
[3, 1, 2, 4] | 41,522 | 41,666 | 0.49767196275 |
[3, 1, 4, 2] | 41,759 | 41,666 | 0.20757932126 |
[3, 2, 1, 4] | 41,841 | 41,666 | 0.73501176018 |
[3, 2, 4, 1] | 41,568 | 41,666 | 0.23049968799 |
[3, 4, 1, 2] | 41,556 | 41,666 | 0.29040464647 |
[3, 4, 2, 1] | 42,060 | 41,666 | 3.72572361158 |
[4, 1, 2, 3] | 41,691 | 41,666 | 0.01500024000 |
[4, 1, 3, 2] | 41,730 | 41,666 | 0.09830557288 |
[4, 2, 1, 3] | 41,708 | 41,666 | 0.04233667738 |
[4, 2, 3, 1] | 41,640 | 41,666 | 0.01622425958 |
[4, 3, 1, 2] | 41,502 | 41,666 | 0.64551432822 |
[4, 3, 2, 1] | 41,670 | 41,666 | 0.00038400614 |
Total | 18.1624633994 |
= 18.1624633994
對於 N 個隨機樣本而言,自由度為 N - 1
而本次實驗有 24 個結果,故自由度為 23
從 Chi-Square Distribution Table 找合適的 P value,我們的自由度為23
可看到 17.187 < 18.1624633994 < 19.021,所以其 P value 介於 0.7 和 0.8 之間
而 設為常見的 0.05
對於要拒絕的虛無假說(),觀察到的結果必須具有統計顯著性,即觀察到的 P value 要小於預先指定的顯著水準
因為 P value(0.7~0.8)> (0.05),統計檢定的結果不拒絕虛無假說()
從圖表來看 shuffle 的頻率是大致符合 Uniform distribution 的
lib/list_sort.c
閱讀 Linux 核心的鏈結串列排序 , list_sort.c
以及 linked list 和非連續記憶體操作
list_sort
一開始會將鏈結串列透過 head->prev->next = NULL;
將頭尾分開
另外在註解中有提到:
意思是說雖然輸入的是循環雙向鏈結串列,但在此函式裡會將其視為單向的
相關變數宣告為以下:
將 head 的第一個節點指派給 list,然後將 pending 指向 NULL
之後會進入 do while 迴圈,他會一步步把 list 裡面的節點移到 pending 中,並將 count +1,直到 list 為空,而 list 和 pending 分別為:
一開始會看到以下 for 迴圈,他的用途是讓 tail 往前移動到要 merge 的位置,而移動的條件是根據 count 的二進制值最小位有多少連續的 1 來移動 tail 多少步
在 for 迴圈執行期間, bits 會慢慢向右移一個位元,所以當迴圈跑完後最小位全部連續的 1 會不見,下表為範例:
count | count 二進位 | tail 往前移動步數 | bits 二進位值在迴圈跑完後 |
---|---|---|---|
0 | 0000 | 0 | 0000 |
1 | 0001 | 1 | 0000 |
2 | 0010 | 0 | 0010 |
3 | 0011 | 2 | 0000 |
4 | 0100 | 0 | 0100 |
5 | 0101 | 1 | 0010 |
6 | 0110 | 0 | 0110 |
7 | 0111 | 3 | 0000 |
再來會根據 if(bits)
判斷是否該在 pending 上進行合併 ( merge ),並依 tail 移動的步數來判斷要合併哪兩個串列,最後從 list 新增一個節點,以下表例:
count | tail 往前移動步數 | bits | pending 狀態 |
---|---|---|---|
0 | 0 | 0000 | 從 list 新增一節點 |
1 | 1 | 0000 | 從 list 新增一節點 |
2 | 0 | 0010 | 合併第 0 跟第 1 個串列,並從 list 新增一節點 |
3 | 2 | 0000 | 從 list 新增一節點 |
4 | 0 | 0100 | 合併第 0 跟第 1 個串列,並從 list 新增一節點 |
5 | 1 | 0010 | 合併第 1 跟第 2 個串列,並從 list 新增一節點 |
6 | 0 | 0110 | 合併第 0 跟第 1 個串列,並從 list 新增一節點 |
7 | 3 | 0000 | 從 list 新增一節點 |
以下圖例是 do while 根據 count 值,list 和 pending 的狀態:
一開始:
count = 0
count = 1
count = 2 ( 紅色為執行 merge )
count = 3 ( 藍色為已排序的串列 )
count = 4
count = 5
count = 6
count = 7
merge
& merge_final
兩者的共通點都是將兩個已排序好的串列做合併。
但差別就是 merge_final
會重建 prev
指標的鏈接,將結果轉換為一個循環雙向鏈結串列,在 list 為空時執行。
在 list_sort.c 裡面有用到 __attribute__((nonnull))
這個函式屬性
__attribute__((nonnull(2,3,4)))
表示 compiler 可以對第 2、3、4 個參數為 NULL 時發出警告。以及 likely
unlikely
這兩個巨集,他們被定義在 /include/linux/compiler.h 中
__built_expect()
是 gcc 的內建函式,用來將 branch 的相關資訊提供給 compiler,告訴 compiler 設計者期望的比較結果,以便對程式碼改變分支順序來進行優化翻閱計算機組織/結構的教科書,從 branch prediction 的角度去確認上述說法。
lab0-c
專案創建並複製 list_sort.c 程式碼到 lab0-c
另外 list_sort.c 裡面有使用到下列兩個巨集,所以也需要加入到程式碼開頭
接著將宣告 list_cmp_func_t 的定義加入到程式碼開頭
根據 list_sort.c 裡的註解實作 cmp_function
The comparison function @cmp must return > 0 if @a should sort after
@b ("@a > @b" if you want an ascending sort), and <= 0 if @a should
sort before @b or their original order should be preserved.
參照 qtest.c 裡的 do_sort
函式實作 do_linux_sort
。
與實作 q_shuffle
一樣的做法,因為要執行 list_sort(NULL, current->q, cmp_function);
,所以要添加下列函式聲明才可以正確編譯 do_linux_sort
。
最後從 qtest.c
裡的 console_init
加入新命令
不過在編譯時出現以下錯誤訊息:
在 list_sort.c 的第 56 行有宣告一個型態為 u8 的變數,意思是 unsigned 8-bit integer,被定義在 /tools/include/linux/types.h 裡,我的做法是直接自己定義
在 Makefile
中,OBJS
變數通常用來存放需要編譯的目標檔案。當新增一個 list_sort.c
檔案後,需要將 list_sort.o
加入到 OBJS
變數中。
這樣在執行 make
命令時,list_sort.c
才會被編譯成list_sort.o
目標檔案。
閱讀並學習如何使用 Perf
在 perf stat 提供的範例中提到將 array[i][j]++
的 i,j 對調改成 array[i][j]++
會同時降低許多 cache reference 和 cache miss 次數,主要是因為:
空間局部性:當一個記憶體區塊被載入快取時,相鄰的區塊也很可能會被載入。
學習如何寫 shell scripts,優點是可以一次執行多個命令
首先新增 test_linux_sort.sh
和 test_sort.sh
兩個 shell scripts 用來執行效能測試
檢測的項目有 cache-misses, branches, instructions 和 context-switches
接著在 ./lab0-c/traces
新增以下兩個目錄,分別存放用來測試 linux_sort
與 sort
的命令檔
RAND_1000.cmd
內容:
輸入 chmod u+x *.sh
為 .sh
檔設定執行權限,否則會遇到 Permission denied
最後輸入 ./test_linux_sort.sh
就會在 traces_perf_linux_sort 目錄下為每個 cmd 檔新增一個 report,點開後即可看到以下效能分析內容
實驗結果
sort
# node | cache-misses | branches | instructions | context-switches | time |
---|---|---|---|---|---|
1000 | 1,2426 | 85,7773 | 578,5872 | 0 | 0.0011827 |
10000 | 7,2960 | 636,5969 | 4503,6733 | 0 | 0.007541 |
100000 | 436,9531 | 6447,4547 | 4,5055,1876 | 10 | 0.1036 |
1000000 | 9829,0049 | 6,7463,1160 | 46,2739,7185 | 8 | 1.2517 |
list_sort
# node | cache-misses | branches | instructions | context-switches | time |
---|---|---|---|---|---|
1000 | 8422 | 84,9436 | 582,1613 | 0 | 0.0010624 |
10000 | 3,2865 | 629,6853 | 4570,8874 | 0 | 0.0063344 |
100000 | 226,5870 | 6387,2396 | 4,5985,1778 | 1 | 0.078921 |
1000000 | 7861,2101 | 6,6845,0006 | 47,4151,1080 | 7 | 1.1422 |
在 Linux 核心的 list_sort 實作 提到,除了 list_sort 是以 bottom up 來實作之外,他相較於一般的 merge_sort 擁有更有效利用 cache 的合併方式,合併時機是當有 個節點時會合併前兩個 ,使其維持著合併:不合併為 2:1 的比例,這樣更能實現空間局部性。
所以從空間局部性來看,list_sort 可以使 cache 中現存的資料更容易被用到,所以 cache miss 在節點數量越多時的差距會越來越大,進而影響執行時間。而 merge sort 通常需要額外的記憶體空間來儲存很多個子串列,然後一次合併,這時通常會發生很多 cache-misses,在空間局部性的表現上較差,甚至導致 cache trashing。
branch 次數的部份,list_sort 是使用迭代的算法,它通常較少涉及到函式的重複呼叫,只透過循環來完成任務,這相較於一般 merge_sort 遞迴的算法 branch 次數會更少,因為遞迴的本質是將大問題分解成小問題,但這會重複呼叫同個函式比較多次