演算法選擇與權衡 (Algorithm Selection and Tradeoffs)
重點總覽 (Overview)
平行程式設計三步驟的第一步是 algorithm selection。同一個問題通常有多個演算法可解,而它們在四個面向上各有優劣,沒有任何一個演算法在所有面向都最好,程式設計者必須為「目標硬體」挑出最佳折衷。
| 項目 | 重點 |
|---|---|
| 演算法三大基本性質 | definiteness(明確)、effective computability(可計算)、finiteness(會終止) |
| 四個比較面向 | 複雜度 (complexity / work)、平行度 (parallelism)、一般性 (generality)、準確度/數值穩定性 (accuracy / numerical stability) |
| 經典權衡 1 | 複雜度 vs 平行度 → Kogge-Stone vs Brent-Kung (Ch.11 Scan) |
| 經典權衡 2 | 一般性 vs 效率 → merge sort vs radix sort (Ch.13 Sorting) |
| 經典權衡 3 | 複雜度 vs 準確度 → DCS vs cutoff summation (Ch.18 電位圖) |
| 範例演算法 | cutoff binning:grid-centric 分解 + 分箱 (binning) + overflow list |
| 量化結果 | DCS work 隨體積平方成長;cutoff binning (SmallBin-Overlap) 比 CPU-SSE 序列版快 17× 且可擴充 |
「最佳演算法」取決於目標平行硬體的特性,以及高複雜度能否用混合策略 (hybrid) 或 thread coarsening(把序列/低複雜度演算法塞進每條 thread)來緩解。
演算法的三大基本性質 (Essential Properties of an Algorithm)
演算法 = 一步步精確描述、且電腦能執行的程序。必須同時具備三性質:
| 性質 | 意義 |
|---|---|
| Definiteness | 每一步都精確陳述,沒有模糊空間 (no ambiguity)。 |
| Effective computability | 每一步都能由電腦實際執行。 |
| Finiteness | 保證會終止 (terminate),不會無限執行。 |
這三性質是「是不是演算法」的門檻,與平行/序列無關。下面的四面向則是「同樣是合法演算法,但哪個更適合平行硬體」的選擇問題。
平行演算法的四個比較面向 (Four Axes of Algorithm Tradeoffs)
給定問題,通常能想出多個演算法,它們在以下面向各異:
| 面向 | 說明 | 偏好 |
|---|---|---|
| Algorithmic complexity / work | 完成同一計算所需的運算步數;步數少 = work efficient | 越低越好 |
| Parallelism exposed | 可同時執行的程度;高平行度 → 用更少的 iteration 完成 | 越高越好 |
| Generality | 適用範圍;能處理多少種輸入/key | 越廣越好 |
| Accuracy / numerical stability | 結果的準確度與數值穩定性 | 越高越好 |
關鍵心法:這四者通常無法兼得。提升一項常以犧牲另一項為代價,這正是「權衡 (tradeoff)」一詞的由來。平行程式設計者的工作就是針對手上的硬體挑出最佳折衷。
三大經典權衡 (Three Classic Tradeoffs)
本書多章已實際比較過演算法,可歸納為三個反覆出現的經典權衡:
| 權衡 | 範例 (章節) | A 方 | B 方 |
|---|---|---|---|
| 複雜度 vs 平行度 | Scan (Ch.11) | Brent-Kung:work-efficient (步數/運算少) | Kogge-Stone:平行度高、iteration 少 |
| 一般性 vs 效率 | Sorting (Ch.13) | Merge sort:comparison-based,適用任何有比較運算子的 key | Radix sort:noncomparison,複雜度低且易平行,但僅限特定 key |
| 複雜度 vs 準確度 | 電位圖 (Ch.18) | DCS:完全準確,但 work 高 | Cutoff summation:犧牲少量準確度換大幅降複雜度 |
複雜度 vs 平行度 (Complexity vs Parallelism)
- Brent-Kung:演算法複雜度低,做同一計算所需 operations 較少 → work efficient。
- Kogge-Stone:暴露更多平行度,能在更少的 iteration 完成。
- 量級對照(N 個元素的 scan):
| 演算法 | Work (運算量) | Steps (深度) |
|---|---|---|
| Kogge-Stone | O(N log N) |
O(log N) |
| Brent-Kung | O(N) |
O(2 log N) |
誰勝出取決於硬體,且高複雜度可被緩解:用 hybrid(兩個平行演算法結合)或把平行演算法與低複雜度的序列演算法經由 thread coarsening 結合。
一般性 vs 效率 (Generality vs Efficiency)
- Radix sort:noncomparison sort,複雜度可低於 merge sort,且高度適合平行化;但只能用於特定型別的 key(不夠一般)。
- Merge sort:comparison-based,任何有良好定義比較運算子的 key 都能用 → 一般性高。
複雜度 vs 準確度 (Complexity vs Accuracy)
- DCS 與 cutoff summation 平行度相同、一般性相同,差別純粹在「複雜度 vs 準確度」。
- DCS 的致命傷:完全準確的方法,計算量隨體積的平方成長 (
O(V²));大體積時即使是大規模平行硬體也會被淹沒。 - Cutoff 的物理洞見:許多 grid 計算基於物理定律,遠處粒子的貢獻極小,可用隱式方法以低很多的複雜度集體處理 → 用很小的準確度損失換大幅效率提升。
複雜度 vs 準確度的權衡不是平行程式特有(序列實作也會碰到),但它會給平行程式設計者帶來額外挑戰(見下節 cutoff binning 的分箱與負載不均問題)。
Cutoff Binning 演算法 (Cutoff Binning Algorithm)
把 cutoff 概念真正搬上 GPU,展示「準確度換複雜度」在平行化時的額外挑戰。
序列 vs 平行的分解差異
| 序列 cutoff | 平行 cutoff binning | |
|---|---|---|
| 分解策略 | atom-centric(一次處理一個原子) | grid-centric(每條 thread 算一個 grid point 的能量) |
| 存取行為 | 原子向外散佈 → scatter | 各 grid point 收集鄰近原子 → gather |
| 是否好平行 | 否(scatter 不利 GPU) | 是 |
序列版很直觀:對每個原子,走訪落在其半徑內的 grid point(grid 是可索引的陣列)。但 atom-centric 的 scatter 存取行為讓它難以平行,故 GPU 上改用 grid-centric 分解。
核心構想:分箱 (binning)
- 先把輸入原子依座標排序進 bins;每個 bin 對應 grid 空間中的一個 box,裝下座標落在該 box 的所有原子。
- 為每個 grid point 定義一個 neighborhood:包含「所有可能對該 grid point 能量有貢獻的原子」的那些 bins。
- 執行時:所有 block 各自走訪自己的 neighborhood bins;block 內所有 thread 一起掃描這些 bin 中的原子,但各自判斷該原子是否落在自己的 cutoff radius 內。
// 概念性 pseudocode:grid-centric cutoff binning kernel
__global__ void cutoffBinningKernel(/* bins, neighborhood lists, grid */) {
int gridPoint = blockIdx.x * blockDim.x + threadIdx.x; // 每 thread 一個 grid point
float energy = 0.0f; // 結果累積在 register (gather)
for (bin in neighborhoodBinsOf(blockIdx)) { // block 走訪自己的 neighborhood
loadBinAtomsIntoSharedMemory(bin); // 協同載入到 shared memory
__syncthreads();
for (atom in sharedBinAtoms) {
if (distance(atom, gridPoint) < cutoff) // 各 thread 個別判斷半徑
energy += contribution(atom, gridPoint);
}
__syncthreads();
}
grid[gridPoint] = energy;
}
ASCII:bins、neighborhood 與 overflow list
Grid 空間切成 bins (每個 bin 容量固定 = capacity)
┌────┬────┬────┬────┐
│ b00│ b01│ b02│ b03│ ● = grid point (一條 thread)
├────┼────┼────┼────┤ neighborhood(●) = ● 半徑內可能有貢獻的所有 bin
│ b10│ b11│●b12│ b13│ = { b01,b02,b03, b11,b12,b13, b21,b22,b23 }
├────┼────┼────┼────┤
│ b20│ b21│ b22│ b23│
└────┴────┴────┴────┘
分箱:每個 atom 依座標放進 home bin
bin 容量足夠 → 放入 bin
bin 已滿 → 改放 OVERFLOW LIST ┐
▼
[ overflow list ] → kernel 結束後由 host 序列補算缺漏的貢獻
分箱的隱患:負載不均 (load imbalance)
- 問題:bins 內原子數不一——有的很多、有的甚至是空的。
- 解法:把 bin 容量設在合理水準(遠小於單一 bin 可能的最多原子數),並維護一個 overflow list:分箱時若原子的 home bin 已滿,就改放進 overflow list。
- kernel 跑完後,overflow list 中的原子需另外處理以補上缺漏的貢獻。
SmallBin-Overlap 的關鍵優化:把「序列處理 overflow 原子」與「下一個 kernel 的執行」重疊 (overlap),藉此把這段序列補算的時間藏進 GPU 計算的影子裡。
擴充性與效能比較 (Scalability & Performance, Fig. 19.2)
以「跑多大體積仍划算」對比各實作 (CPU-SSE3 為序列 cutoff 基準):
| 體積規模 (ų) | 誰最快 / 現象 | 原因 |
|---|---|---|
| ~1,000(小) | CPU (SSE) 快過 DCS kernel | 工作量不足以餵飽 GPU |
| 2,000 – 500,000(中) | DCS kernel 明顯勝 host | 大規模平行性發揮 |
| ~1,000,000(大) | DCS 反而比序列 CPU 還慢! | DCS 複雜度高,work 隨體積平方暴增,淹沒硬體資源 |
三種 binning 實作:
| 實作 | 特點 | 結果 |
|---|---|---|
| SmallBin | neighborhood bin list,讓不同 block 處理不同 neighborhood | 多了把原子從 global → shared memory 的指令開銷;中體積下原子本就不多,「少看一些原子」省下的不足以抵銷開銷 |
| SmallBin-Overlap | 將序列 overflow 處理與下一個 kernel 執行重疊 | 比 SmallBin 略有但明顯的改善 |
| → 整體 | — | 比高效序列 CPU-SSE cutoff 快 17×,並在大體積維持同樣的可擴充性 |
主旨:演算法複雜度決定可擴充性。DCS 平行度雖高,但 O(V²) 的工作量讓它在大體積時崩潰;改用較低複雜度的 cutoff 演算法(犧牲少量準確度)才換到「大規模仍可用」的擴充性。這呼應 19-Parallel-Programming-And-Computational-Thinking/01-Goals-of-Parallel-Computing-and-Amdahls-Law 中「用平行換更大問題 / 更好解」的目標。
考試/面試重點 (Exam / Test Patterns)
| 情境 / 關鍵字 | 答案 / 技巧 |
|---|---|
| 「演算法必須具備哪三性質?」 | definiteness、effective computability、finiteness |
| 「平行演算法選擇要比較哪些面向?」 | 複雜度(work)、平行度、一般性、準確度/數值穩定性;無單一演算法全勝 |
| Kogge-Stone vs Brent-Kung 屬哪種權衡? | 複雜度 vs 平行度;Brent-Kung work-efficient,Kogge-Stone 平行度高(iteration 少) |
| radix sort vs merge sort 屬哪種權衡? | 一般性 vs 效率;radix 快但限特定 key,merge 一般(任何有比較運算子的 key) |
| DCS vs cutoff summation 屬哪種權衡? | 複雜度 vs 準確度;兩者平行度與一般性相同 |
| 「DCS 為何在大體積崩潰?」 | work 隨體積平方 O(V²) 成長,大體積時甚至慢過序列 CPU |
| 「cutoff 為何能省複雜度?」 | 物理上遠處粒子貢獻極小,可用隱式低複雜度方法集體處理 |
| 「為何 GPU 上用 grid-centric 而非 atom-centric?」 | atom-centric 是 scatter 不利 GPU;grid-centric 是 gather(結果累積在 register) |
| 「binning 的隱患與解法?」 | bins 原子數不均(load imbalance);設小 bin 容量 + overflow list,kernel 後序列補算 |
| 「SmallBin-Overlap 優化什麼?」 | 把序列 overflow 處理與下個 kernel 執行重疊;對 CPU-SSE 序列版達 17× |
| 「如何緩解高複雜度演算法?」 | hybrid(結合兩演算法)或 thread coarsening(每 thread 內塞低複雜度序列演算法) |
Related Notes
- 19-Parallel-Programming-And-Computational-Thinking/01-Goals-of-Parallel-Computing-and-Amdahls-Law
- 19-Parallel-Programming-And-Computational-Thinking/03-Problem-Decomposition-Output-vs-Input-Centric
- 19-Parallel-Programming-And-Computational-Thinking/04-Computational-Thinking-and-Parallelization-Approaches
- 11-Prefix-Sum-Scan/02-Work-Efficient-Scan-Brent-Kung-and-Coarsening
- 13-Sorting/03-Parallel-Merge-Sort-and-Other-Methods
- 18-Electrostatic-Potential-Map/03-Cutoff-Binning-for-Scalability