contributed by < aftuta85
>
這小節介紹測驗題版本 Timsort 為主。
首先,timsort() 在 while
迴圈中使用 find_run()
將序列切割成一個個獨立的 run,這些 run 是按照遞增或遞減的順序排列。舉例來說,如果序列是 {5, 4, 3, 6, 1, 7, 8},它將被切割成 {5, 4, 3}、{6, 1}、{7, 8} 這樣的 run。每次切割出一個 run,stk_size
就會記錄目前 run 的總數。在每一輪 find_run()
後,都會呼叫 merge_collapse()
來進行 run 的合併。merge_collapse()
程式碼如下:
合併規則如下:
持續進行上述步驟 (切 run、合併),直到 find_run()
將整個序列切完為止。
接下來透過 merge_force_collapse()
將尚未合併的 run 合併:
經過 merge_force_collapse()
合併後,可能會剩下 2 個以下的 run。若只剩下 1 個 run,則直接透過 build_prev_link()
將 head 與合併後的 list 接回,形成環狀雙向鏈結串列(在合併過程中 prev
可能會斷掉,或用來記錄當前 run 的大小)。
若在最後 stk_size == 2
,則透過 merge_final()
將 2 個 run 依據大小合併為 1 個 run 後,在呼叫 build_prev_link()
將其回復為環狀雙向鏈結串列。
在 trace code 和補完程式碼的過程中,讓我學習到的是把 prev
指標拿來存 run 長度的用法:
下面程式碼部份有疑惑:
目前測驗題版本的 Timsort 尚未考慮到 minrun 以及 Galloping mode 的實作,僅以遞增或遞減為分割 run 的依據。因此,應該改進兩個方面:
Galloping mode 用於 linked list 還沒有想到怎麼做,因為怎麼樣都要逐一訪問節點,那就直接用one-pair-at-a-time 模式即可。
程式碼 timsort.c
新增了 minrun
機制,並且在 find_run()
過程中,若 run size
< minrun
,則透過 binary insertion 將新節點插入當前 run。
在實作 binary insertion 時,在做插入時對於節點的 next
、prev
操作上有一些混亂,目前的版本或許要在檢查一下。
目前的實作 (timsort) 與原始版本 (timsort_orig) 的比較:
這邊的 SAMPLES 是 100000
目前以 clock()
計算執行時間,如下:
有個疑惑是雖然改進版本的比較次數比原始版本少,但執行時間卻比較長一點,雖然用 clock() 有其他因素干擾,但對每次結果都是相同趨勢感覺疑惑
排除實作差異,改進版本只多了計算 minrun 跟資料長度,試過把計算長度功能也在原始版本做,但依舊差不多結果。或許要再檢查一下目前實作部分有沒有問題。後續再使用其他工具來做測試。
find_nth_bit()
函式的功能是在給定的 size 範圍內找到第 n 個設置為 1 的位元,流程如下:
size
小於或等於 BITS_PER_LONG
,則使用 `fns()`` 函式找出第 n 個位元的位置。size
大於 BITS_PER_LONG
,則使用 FIND_NTH_BIT
巨集以每 BITS_PER_LONG
單位進行查找。在 fns()
中,通過 while
迴圈,每次透過__ffs()
找出第一個最低有效位。如果這個位元不是要找的第 n 個,則清除該位元並重複上述步驟。
__ffs()
定義如下:
在假設為 64 位元環境下,__ffs()
依序檢查 word
的 32 個位元、16 個位元、8 個位元、4 個位元、2 個位元和 1 個位元中是否有 1。
下面以一個 16 位元環境為範例,所以會依序檢查8 個位元、4 個位元、2 個位元和 1 個位元:
word = 1000100000000000 // (word & 0xff) == 0, num = 0 + 8 = 8
word = 0000000010001000 // (word & 0xf) != 0
word = 0000000010001000 // (word & 0x3) == 0, num = 8 + 2 = 10
word = 0000000000100010 // num = 10 + 1 = 11
如果 size
大於 BITS_PER_LONG
,則利用 FIND_NTH_BIT
巨集透過 for
迴圈以每 BITS_PER_LONG
為單位去找出累積的 bit 1 數量是否已達到要求。在這邊利用 hweight_long()
計算出當前資料段有多少個 bit 1。當確定到這段資料段已經達到數量要求後,跳到 found
,在利用上述 fns()
找出實際位置。
hweight_long()
透過將 64 個位元拆分為 32 個位元 + 32 個位元、16 個位元 + 16 個位元,直到每 8 個位元一組,計算每組中有多少個 1,然後將這些數量加總回到 64 個位元。
下面以一個 32 bits 資料 10000000000010000111000000000001
為例:
// __const_hweight16(w) + __const_hweight16((w) >> 16)
0111000000000001 1000000000001000
// __const_hweight8(w) + __const_hweight8((w) >> 8)
00000001 01110000
// __const_hweight8(w) + __const_hweight8((w) >> 8)
00001000 10000000
return 1 + 3 + 1 + 1 = 6
在 small_const_nbits(nbits) 使用 __builtin_constant_p() 的目的是?