排序 (Sorting) 練習題 (Practice - Sorting Foundations and Parallel Radix Sort)
Related Concepts
- 13-Sorting/01-Sorting-Foundations-and-Parallel-Radix-Sort
- 13-Sorting/02-Optimizing-Radix-Sort
- 13-Sorting/03-Parallel-Merge-Sort-and-Other-Methods
| 關鍵字 / 情境 | 答案要點 |
|---|---|
| 排序的兩個必要條件 | (1) 輸出 nondecreasing/nonincreasing 順序;(2) 輸出是 input 的 permutation |
| stable sort 用途 | 保留相同 key 原始順序 → 支援 multi-key cascaded sort(先排 secondary 再排 primary) |
| radix sort 類別 / 複雜度 | noncomparison-based,可 < O(N·logN);只適用整數等可拆 digit 的 key |
| comparison-based 下界 | O(N·logN)(必須做足夠次比較) |
| N-bit key、1-bit radix iteration 數 | N 次;r-bit radix 為 ⌈N_bits / r⌉ 次 |
| iteration 間 vs iteration 內 | iteration 間序列(依賴前一步完整結果);平行機會在單一 iteration 內 |
| destination of a 0 | i − (# ones before),其中 i = key index |
| destination of a 1 | inputSize − (# ones total) + (# ones before) |
# ones before 怎麼算 |
對 bit 陣列做 exclusive scan;# ones total 為其副產品 |
為何 exclusiveScan 放邊界檢查外 |
scan 內含 barrier sync,需所有 thread active,否則死結 / 結果錯 |
| 基礎 kernel 寫回慢的原因 | output[dst]=key 是 scatter,連續 thread 寫非連續位址 → uncoalesced |
| coalescing 優化 (13.4) | block 在 shared memory 做 local sort 排成連續桶,再整段 coalesced 寫回 global |
| local bucket 全域起始位置 | 對「各 block local bucket 大小表 (row-major)」做一次 global exclusive scan |
| r-bit radix 的桶數 / local scan 數 | 2^r 個 bucket;local sort = r 次連續 1-bit local scan |
| radix 取大的代價 | 桶多段小 → coalescing 變差;scan table (2^r × #blocks) 變大 → global scan 開銷上升 |
| thread coarsening 收益 | local buckets 變大 → coalescing↑;block 數變少 → scan table 變小 → global scan 開銷↓ |
| merge sort 平行來源 | 早期 stage 跨 merge 平行;晚期 stage merge 內平行(co-rank 切分) |
| 其他平行排序法 | odd-even transposition O(N²);sorting networks(bitonic)O(N·log²N);sample sort(top-down) |
| LSD vs MSD radix | LSD 每步 global shuffle;MSD 由高位切桶,操作越來越 local,適合超大序列 |
Question 1 - 排序的兩個必要條件與 stable [recall]
情境/題目:任何 sorting algorithm 必須滿足哪兩個條件?另外,什麼是 stable sort,它在什麼情況下是必要的?
兩條件:(1) 輸出為 nondecreasing 或 nonincreasing 順序;(2) 輸出是 input 的 permutation(只重排、不增不減元素)。
Stable sort 在相同 key 時保留原始出現順序;它是 multi-key cascaded sort 的必要條件——先依 secondary key 排,再依 primary key 排,第二次排序會保留第一次建立的順序。
Question 2 - Radix Sort 的本質與迭代結構 [recall]
情境/題目:Radix sort 屬於哪一類排序?以 1-bit radix 排 N-bit key 需要幾次 iteration?為什麼 iteration 之間必須序列執行,而平行機會在哪裡?
Radix sort 是 noncomparison-based,依 radix(base)把 key 分配到 buckets,逐 digit 重複且每次 iteration 都 stable。N-bit key、1-bit radix 需 N 次 iteration(本章為 LSD:LSB → MSB)。
每個 iteration 都依賴前一個 iteration 的完整結果,故 iteration 之間必須序列;平行的機會在單一 iteration 之內(host 對每個 bit 呼叫一次 kernel)。
Question 3 - Destination Index 公式 [recall]
情境/題目:在一次 1-bit radix iteration 中,落 0 bucket 與落 1 bucket 的 key,各自的 destination index 公式為何?令
i = key index、inputSize = N。
落 0 bucket:destination = i − (# ones before)(= 自己之前有幾個 0)。
落 1 bucket:destination = N − (# ones total) + (# ones before)(所有 0 必須排在所有 1 之前,故先跳過全部 # zeros total = N − # ones total,再加上自己之前的 1 的個數)。
唯一非平凡的量是 # ones before。
Question 4 - 用 Exclusive Scan 取得 # ones before [recall]
情境/題目:
# ones before與# ones total各自如何取得?為什麼用 exclusive scan 而不是 inclusive scan?
把每個 key 取出的 bit(0/1)當作輸入做 exclusive scan,結果每格 = 該位置之前所有 bit 的和 = # ones before(正是 destination 公式需要的「不含自己」的前綴和,故用 exclusive 而非 inclusive)。
# ones total 則作為 scan 的副產品取得(例如 bits[N],或最後一格 inclusive 結果)。
Question 5 - 為何 Scan 必須放在邊界檢查之外 [recall]
情境/題目:在 Fig. 13.4 的 kernel 中,
exclusiveScan(bits, N)為何要寫在if (i < N)邊界檢查之外?若要在整個 grid 範圍同步做 scan,有哪兩種做法?
因為 scan 過程中 thread 可能要做 barrier synchronization(__syncthreads 或 grid-wide barrier),必須所有 thread 都 active;若把 scan 放在邊界檢查內,被檢查擋掉的 inactive thread 缺席會造成 barrier 死結 / 結果錯誤。
兩種 grid-wide 做法:(1) 用 single-pass / grid-wide scan 技巧維持單一 kernel launch;(2) 拆成 3 個 kernel(scan 前處理 → 獨立 scan → scan 後處理),代價是每個 iteration 變 3 次 grid launch。
Question 6 - Comparison vs Noncomparison 複雜度 [recall]
情境/題目:comparison-based 排序的複雜度下界是多少?radix sort 為何能突破它?代價是什麼?
Comparison-based 排序不可能優於 O(N·logN),因為必須執行足夠多次的兩兩比較。
Radix sort 能更快(可 < O(N·logN)),是因為它不靠比較,而是靠 digit 分桶;代價是無法泛化到任意 key 類型,通常只適用整數等可拆 digit 的 key。
Question 7 - Merge Sort 的兩層平行性 [recall]
情境/題目:平行 merge sort 用 merge tree 自底向上合併。在早期 stage 與晚期 stage,平行性的主要來源分別是什麼?
早期 stage:有很多獨立的 merge 操作(每個 merge 的 key 數少)→ 平行來源是跨 merge 平行(把不同 merge 分給不同 thread block)。
晚期 stage:獨立 merge 數變少、但每個 merge 的 key 數變多 → 平行來源是 merge 內平行(用第 12 章的 co-rank function 把單一大 merge 切成許多獨立段)。兩端都能讓所有 thread block 有事做。
Question 8 - 其他平行排序方法 [recall]
情境/題目:簡述 odd-even transposition sort、sorting networks、sample sort 的特徵與複雜度,並說明 LSD 與 MSD radix sort 的關鍵差異。
Odd-even transposition sort:交替比較不重疊的 even/odd pair,類平行 bubble sort,O(N²),大序列效率差。
Sorting networks(Batcher's bitonic、odd-even merge sort):固定的比較/交換序列,天生平行,O(N·log²N);漸進雖比 merge sort 差,但小序列上常最快。
Sample sort:top-down,選 p−1 splitter 切成 p 個有序桶,各桶獨立排序後直接 concatenate;適合超大序列 / 多 GPU。
LSD vs MSD:LSD(本章)由低位開始,每步需 global shuffle;MSD 由高位切桶,操作越來越 local,適合超大序列。
Question 9 - 計算 Destination Indices [application]
情境/題目:input(8 個 key,index 0..7)取出的 LSB bits 為
[1, 0, 1, 1, 0, 0, 1, 0]。請算出# ones before陣列、# ones total,以及 index 2(bit=1)與 index 4(bit=0)兩個 key 的 destination index。
exclusive scan(# ones before)= [0, 1, 1, 2, 3, 3, 3, 4],# ones total = 4(共 4 個 1)。
index 2,bit=1:dst = N − total + before = 8 − 4 + 1 = 5。
index 4,bit=0:dst = i − before = 4 − 3 = 1。
(所有 0 桶 key 佔 index 0..3,1 桶 key 佔 index 4..7。)
Question 10 - r-bit Radix 的計算 [application]
情境/題目:要排序 32-bit 的整數 key,改用 4-bit radix。請問:(a) 需要幾次 iteration?(b) 每次 iteration 每個 block 有幾個 local bucket?(c) 每個 block 的 local sort 需要做幾次 1-bit local exclusive scan?(d) global bucket-size 表有幾列?
(a) iteration 數 = ⌈N_bits / r⌉ = ⌈32 / 4⌉ = 8 次。
(b) local bucket 數 = 2^r = 2^4 = 16 個。
(c) r-bit local sort = r = 4 次連續 1-bit local exclusive scan(iteration 間無跨 block 協調)。
(d) global scan 的 table 有 2^r = 16 列(每列對應一個 bucket,欄數 = block 數)。
Question 11 - Shared Memory Local Sort 的全域位移 [application]
情境/題目:在 13.4 的 coalescing 優化中,每個 block 在 shared memory 做完 local sort 後,如何決定自己的 local 0-bucket 與 local 1-bucket 在 global output 中的起始位置?請描述所用的資料結構與運算。
每個 block 算出各 local bucket 的 key 數,填進一張 row-major bucket-size 表(先放所有 block 的 0-bucket size,再放所有 block 的 1-bucket size):[s00 s01 s02 s03 | s10 s11 s12 s13]。
對這張線性化的表做一次 global exclusive scan,結果即為各 block 各 local bucket 的 global 起始位移。
規則:某 block 的 local 0-bucket 接在所有前面 block 的 0-bucket 之後;local 1-bucket 接在全部 block 的 0-bucket + 前面 block 的 1-bucket 之後。寫回時每個 thread 依 threadIdx 落在哪個 local 桶,取對應位移連續寫回。
Question 12 - Coalesced Local Sort vs Naive Scatter [analysis]
情境/題目:基礎 kernel 直接
output[dst] = key為何 coalescing 很差?13.4 的「shared memory local sort 後再寫回」如何改善?這個優化把成本從哪裡移到了哪裡?
基礎(scatter):相鄰 thread 的 key 落在不同 bucket(0/1 交錯),consecutive thread index 不對應 consecutive memory location,一個 warp 內位址跳來跳去 → 需發出多次 memory request(uncoalesced)。
優化:採用 Ch.6 的第 3 法——每個 block 先在 shared memory 做 block-level local sort,把 0/1 桶排成連續段,再讓連續 thread 把整段以 coalesced 方式寫回 global。
成本轉移:把原本 grid-wide 的 scatter 降為 block 內 local exclusive scan(快很多);代價只是多算一張小小的 bucket-size 表的 global exclusive scan(以求各 block local bucket 的全域起始位移)。淨效果是用一次便宜的小 scan 換回大量 coalesced 寫入。
Question 13 - Radix 大小取捨與 Thread Coarsening [analysis]
情境/題目:增大 radix(用更多 bit/iteration)能減少 iteration 數,為什麼不能把 radix 取得無限大?另外,thread coarsening 為何能同時改善 coalescing 又降低 global scan 開銷?coarsening 在什麼前提下才划算?
大 radix 的代價:雖然 iteration 數降為 ⌈N_bits/r⌉(grid launch / global 存取 / global scan 次數變少),但 (1) 桶數 = 2^r 變多、每桶 key 變少 → 寫回段變小 → coalescing 惡化;(2) global scan 的 table 大小 ∝ 2^r × #blocks 變大 → scan 開銷上升。故需在「iteration 數」與「coalescing + global scan 開銷」之間取平衡。
Thread coarsening:每個 thread 負責多個 key → block 數變少、每 block key 變多。這同時 (1) 讓 local buckets 變大 → 連續 thread 更可能寫到連續位址(coalescing↑),並 (2) 因 scan table ∝ bucket 數 × block 數,block 數變少 → table 變小 → global scan 開銷↓。
划算前提:當這些 block 本來就會被硬體序列化執行時(此時減少並行度不損失真正的平行收益,卻省下重複付出的 coalescing 與 scan 成本);若 block 真能完全並行,這代價未必該省。
| 主題 | 核心重點 |
|---|---|
| 排序定義 / stable | nondecreasing/nonincreasing + permutation;stable 保相同 key 順序 → multi-key cascaded sort |
| Radix sort 本質 | noncomparison、依 digit 分桶、每 iteration stable;N-bit / 1-bit radix 需 N 次 iteration(LSD) |
| 平行粒度 | iteration 間序列(依賴前一步);平行在單一 iteration 內,one-thread-per-key |
| Destination 公式 | of 0 = i − #ones_before;of 1 = N − #ones_total + #ones_before |
| 核心工具 | 對 bit 做 exclusive scan 得 #ones before,total 為副產品;scan 須放邊界檢查外(barrier 需全 active) |
| 複雜度 | comparison-based 下界 O(N·logN);radix 可 < O(N·logN) 但限特定 key |
| Coalescing 優化 (13.4) | block 在 shared memory local sort 排連續桶 → coalesced 寫回;多算一張 bucket-size 表的 global scan |
| Radix 大小 (13.5) | r-bit → ⌈N_bits/r⌉ 次 iteration、2^r 桶、r 次 local scan;太大 → coalescing↓、scan table↑ |
| Thread coarsening (13.6) | 每 thread 多 key → block↓、local bucket↑ → coalescing↑ 且 scan table↓;block 被序列化時最划算 |
| Merge sort | comparison-based、merge tree;早期跨 merge 平行、晚期 merge 內平行(co-rank) |
| 其他方法 | odd-even transposition O(N²);sorting networks(bitonic)O(N·log²N) 小序列快;sample sort/MSD radix top-down |
| 實務 | 多數人直接用 Thrust 等函式庫,而非手寫 sorting kernel |