教材:10710周志遠教授平行程式 https://www.youtube.com/playlist?list=PLS0SUwlYe8cxqw70UHOE5n4Lm-mXFXbZT 20250830 筆記 內容可能有錯僅供參考 10C~10D Synchronization Tools & Open Multi-Processing(OpenMP)Synchronization Construct 今日大綱 Load balancing and termination Static Load balancing Dynamic Load balancing Centralized Work Pool Decentralized Work Pool Fully Distributed Work Pool ### **Load balancing and termination** **負載平衡的重要性**: - 對於平行程式而言,**整個程式的執行時間是由最慢的任務結束時間所決定**,而非平均時間。 - 如果負載不平衡,某些處理器會被分配到大量或計算複雜的任務,而其他處理器則閒置,這會顯著**加長整體執行時間**。 - 因此,**優化平行程式最基本的第一點就是希望負載是平衡的**,以實現最短的執行時間。 **中止偵測的重要性**: - 在平行程式中,**所有的工作者都需要知道何時可以結束其程式**。 - 如果採用分散式寫法且沒有單一的「primary」存在,中止偵測會變得非常複雜。 - MPI 等程式模型中,每個處理 process 必須能知道何時終止,否則整個任務將無法正確結束。必須有辦法偵測所有計算何時完成。 ### **靜態負載平衡 (Static Load-Balancing)** - **定義**:靜態負載平衡是指在**程式執行前**,就根據預先確定的分配規則 (predetermined assignment) 來決定任務 (task) 與處理 process 之間的映射關係。 - **決策與執行時間無關**:無論採用哪種預先確定的演算法或策略,其決策都與實際的執行時間無關。 - **例子**: - **輪流分配 (Round-robin)**:任務依序分派給各處理 process 。 - **隨機分配 (Randomized)**:任務依據隨機結果分派,但在執行前已決定好,與執行狀態無關。 - **加權分配 (Weighted)**:基於事先的估計,將不同權重的任務分派給處理 process ,但估計的準確性是個問題。 - **缺點**: - **不考慮真實執行時間**:靜態方法無法考慮到每個任務實際的計算時間。 - **估計困難**:預估每個任務的執行時間非常困難且不準確,特別是通信延遲等因素難以預估。 - **Mandelbrot 集合的計算量不可預知**:Mandelbrot 集合中每個像素的計算量在執行前根本無法得知,因為它取決於該點多快發散。黑色的區域(屬於 Mandelbrot 集合的點)需要進行最大次數的迭代才能判斷,計算量遠大於淺色區域。如果單純按像素數量分配任務,會導致負載嚴重不平衡。 - 因此,除非計算非常規律,否則靜態負載平衡難以有效解決負載平衡問題。 ### **動態負載平衡 (Dynamic Load-Balancing)** - **定義**:動態負載平衡是指**在程式運行時 (run time)**,根據任務的實際執行時間來決定排程決策。 - **機制**:其核心概念是「**先完成者先搶** (first-finished, first-served)」——任何一個處理 process 只要完成當前任務,就立即領取下一個可用任務。 - **優點**: - **適用於計算量不一的任務**:特別適合那些任務計算量長短不一的情況。 - **實現良好平衡**:如果任務或資料區塊 (data partition chunks) 夠小,動態排程最終會使得所有處理器的負載趨於平衡。 - **實作方式**:可分為三種類型:**集中式 (centralized)**、**去中心化 (decentralized)** 和**完全分散式 (fully distributed)**。 ### **Centralized Work Pool** 在集中式工作池模型中,有一個**主導者**負責分派任務和收集結果。根據您的要求,我們將其稱為「Primary」**,而其他執行計算的處理 process 稱為**「Secondary Process」。 **核心概念**: 一個「Primary」負責管理所有待計算的任務,並在執行時才決定將任務分派給哪一個「Secondary Process」,從而實現動態排程。這種方法尤其適合像 MPI 這種處理 process 數量固定的程式模型。 **Primary 的程式碼邏輯** : ``` // Primary Process 程式碼 count = 0; // 目前手上有任務的 Secondary Processes 數量 row = 0; // 已經發送的任務編號 // 步驟一:向每個 Secondary Process 發送初始任務 (row) for (k=0; k<num_proc; k++) { // 遍歷所有 Secondary Processes send(row, P_i, data_tag); // 向 P_i (這裡應該是 P_k) 發送一個 row 任務 count++; // 活躍的 Secondary Processes 數量加一 row++; // 準備發送下一 row } // 步驟二:主迴圈,接收結果並發送新任務或終止訊號 do { // 接收來自任意 Secondary Process 的結果 recv(&slave, &r, color, P_ANY, result_tag); // 接收來自任何 Secondary Process (P_ANY) 的結果 count--; // 一個 Secondary Process 完成任務,活躍數量減一 // 判斷是否還有新任務可發送 if (row < num_row) { // 如果還有未發送的 row 任務 send(row, P_slave, data_tag); // 向剛完成任務的 Secondary Process (P_slave) 發送下一 row 任務 count++; // 活躍數量加一 row++; // 準備發送再下一 row } else { // 如果所有 row 任務都已發送完畢 send(row, P_slave, terminate_tag); // 向 Secondary Process (P_slave) 發送終訊號 } display(r, color); // 顯示已完成的 row 結果 } while(count > 0); // 迴圈條件:當前仍有活躍的 Secondary Processes ``` - **`count` 變數**:代表目前手頭上還有工作、尚未傳回結果的 **Secondary Processes 數量**。這個變數對於**正確的中止偵測**至關重要,Primary 會持續接收訊息直到 `count` 歸零,確保所有 Secondary Process 的結果都被接收,並且所有訊息交換都已完成。 - **`row` 變數**:代表 Primary 已經分派出去的 row 數編號。 - **初始分派**:Primary 會先向每個可用的 Secondary Process 發送一個初始任務(一 row 圖像計算),以啟動它們。 - **主迴圈 (Work-Stealing / Dynamic Assignment)**:Primary 在一個 `do-while` 迴圈中運作。它會不斷地接收來自任何 Secondary Process 的計算結果 (`recv(&slave, ..., P_ANY, result_tag)`) 。 - 每當收到一個結果,`count` 就會減一 。 - Primary 會檢查是否還有未分配的 row (`row < num_row`)。如果還有,它會將下一 row 任務 (`data_tag`) 分配給剛完成任務的 Secondary Process,並將 `count` 加一、`row` 加一 。 - 如果所有 row 都已分配完畢,Primary 則會向該 Secondary Process 發送一個**終止訊號 (`terminate_tag`)**,告知它沒有更多工作了 。 - `display(r, color)` 則用於將計算出的 row 顯示出來 。 - **終止條件**:Primary 的 `do-while` 迴圈會持續進行,直到 `count` 變為 0 。這表示所有 Secondary Processes 都已經完成了他們的工作,並已將結果傳回給 Primary,或者已經收到了終止訊號。這種集中式的架構使得**中止偵測變得相對簡單和明確**。 **Secondary Process 的程式碼邏輯** : ``` // Secondary Process P(i) 程式碼 recv(&row, P_primary, source_tag); // 從 Primary 接收初始任務 while (source_tag == data_tag) { // 只要接收到的是數據任務 (非終止訊號),就持續執行 c.imag = min_imag + (row * scale_imag); for (x=0; x<640; x++) { c.real = min_real + (x * scale_real); color[x] = cal_pixel(c); // 計算單一 row 的像素顏色 } send(i, row, color, P_primary, result_tag); // 將處理 process ID、 row 和結果發送回 Primary recv(&row, P_primary, source_tag); // 請求下一個任務 } // 當 source_tag 不再是 data_tag (即收到 terminate_tag) 時,迴圈結束,Secondary Process 終止 ``` - **接收任務**:每個 Secondary Process 會首先從 Primary 接收一個任務 (`recv(&row, P_primary, source_tag)`) 。 - **任務迴圈**:Secondary Process 進入一個 `while` 迴圈,只要收到的 `source_tag` 是 `data_tag`(表示是一個計算任務),它就會持續執行 。 - 在迴圈內部,它會遍歷一 row 中的所有像素,為每個像素計算其複數 `c`,然後呼叫 `cal_pixel(c)` 函數來決定該像素的顏色 。 - 完成一 row 的計算後,Secondary Process 會將其自身的 ID、計算完成的 row 號以及該 row 的所有像素顏色,透過 `send()` 函數回傳給 Primary (`send(i, row, color, P_primary, result_tag)`) 。 - 接著,它會再次呼叫 `recv()` 函數,**請求 Primary 分派下一個任務** 。 - **終止**:當 Secondary Process 接收到的 `source_tag` 不再是 `data_tag`(表示收到了 Primary 發出的 `terminate_tag` 終止訊號)時,`while` 迴圈就會結束,該 Secondary Process 便會正確終止 。 這種集中式工作池的方法確保了任務的動態分配,使得計算量不均勻的 Mandelbrot 集合能夠實現良好的負載平衡,同時透過 Primary 的 `count` 變數和終止訊號,有效地管理了所有處理 process 的中止狀態。 ### Decentralized Work Pool - **與 Centralized 的關係**:Decentralized 其實可以看作是 Centralized 的一種變形,其基本架構與 Centralized 相似,主要目的是透過特定方式來減輕 single point of failure 的問題。 - **核心理念**:仍然是 **primary/secondary 架構**,但 **不再只有一個 single primary**,而是有多個 **primary**。 - **多個 primary 的角色可能性**: - **階層式 (Hierarchical)**: - 系統中會有一個 **主要的 primary (main primary)**,下面則有多個 **次要的 primary (secondary primary / mini-primary)**。 - 工作分配是階層式的:**worker** 會先向其 **局部的 primary** 請求工作;如果局部工作耗盡,該 **次要 primary** 才會再向 **上層的主要 primary** 請求工作,如此一層一層往上要。 - 這種方式在實際系統中是有效且簡單的解決方案。 - **潛在問題**:在分配過程中,仍可能出現某些 **次要 primary** 被分配到較多計算工作,導致不平衡。 - **地位對等 (Peer-to-peer primaries)**: - 這些 **primary** 之間的地位完全相同,每個 **primary** 都在做相同的事情。 - **worker** 可以任意找任何一個 **primary** 請求工作。 - **優點**:相較於階層式,可能更平衡,因為每個 **primary** 都可以做任何事情。 - **缺點**:**primary** 之間因為地位對等,會有較多的 **同步化 (synchronization) 開銷**,溝通量會大一點。 - **優缺點權衡**:越 Centralized,single point of failure 和 bottleneck 狀況越嚴重;越分散(接近 Distributed),這些問題會越少,但實作會越複雜,且終止條件判斷會越困難。 ### **Fully Distributed** Work Pool - **核心定義**:完全對等,沒有誰是 **primary**、誰是 **secondary** 的概念,所有參與者都是對等的 **peer-to-peer (P2P)** 關係。 - **架構特點**: - 每個 **worker** 都會有自己的 **本地工作池 (local work pool, WP)** 和 **queue** 來處理工作。 - **最重要的一點**:**worker** 之間可以 **主動地請求 (request) 或分配 (push) 工作**,以達成負載平衡 (load balance)。 - 工作池中的任務是散落的,需要透過各個處理器之間的溝通來達成平衡。 - **負載平衡的兩種實作方式**: - **接收者主動式 (Receiver-initiated / Work Stealing)**: - 由工作量較輕 (lighter loaded) 的 **worker (接收者)** 主動發出請求,從工作量較重 (heavily loaded) 的 **worker** 那裡「偷取」工作來做。 - **適用情境**:當整個系統的平均負載較高,只有少數 **worker** 負載很低時。這樣只有少數需要工作的 **worker** 會產生請求流量。 - 這種方式有時在文獻中被稱為 **"Work Stealing" (工作竊取)**。 - **發送者主動式 (Sender-initiated / Work Pushing)**: - 由工作量較重 (heavily loaded) 的 **worker (發送者)** 主動觸發負載平衡的動作,將工作「推」給其他 **worker**。 - **適用情境**:當整個系統的平均負載較輕,大部分 **worker** 都很輕時。這樣只有少數負載重的 **worker** 會產生請求流量。 - **選擇考量**:通常只會實作其中一種,選擇取決於整個系統的平均負載狀況。 - **挑戰與複雜性**: - **額外的溝通開銷**:為了達到平衡,**worker** 之間需要大量的溝通,這會增加網路流量 (network traffic)。 - **判斷負載狀況**:**process** 本身並不知道自己是輕載還是重載,除非它有 **全域視圖 (global view)**,否則難以判斷應向誰請求或把工作推給誰。這可能需要採取 **sampling** 等方法來近似判斷(例如隨機詢問幾個 **worker**,找最重的來「偷」工作)。 - **非立即性效果**:負載平衡往往需要多次迭代 (iterations) 和溝通才能達到平衡狀態,效果不是立即顯現。 - **優點**: - **沒有 bottleneck**:不會卡在特定的 **primary** 或 **secondary** 身上。 - **高擴展性 (Scalability)**:當系統規模非常大時,Fully Distributed 是一個很好的解決方案。 - **最短路徑演算法的 Fully Distributed 實作 :** - **演算法原理**: - 基於迭代更新,每個 **vertex (頂點)** 都會不斷判斷其最短路徑值是否被更新。 - 如果一個 **vertex (vi)** 的最短路徑值被更新,它就會通知其鄰居 (neighbor) **(vj)**,讓鄰居也檢查自己的最短路徑值是否需要更新。 - **更新方式**:將從 source 到 **vi** 的最短路徑值,加上 **vi** 到 **vj** 邊的權重 (weight),如果這個新值比 **vj** 目前記錄的最短路徑值還小,就更新 **vj** 的值。 - 這個過程會一直重複,直到沒有任何 **vertex** 的值再被更新,也就不再產生新的訊息,演算法便結束。 - **平行化實作**: - 每個 **vertex** 都在做相同的事情,因此非常適合平行化。 - 每個 **worker**(對應一個或多個 **vertex**)可以獨立執行這個邏輯,接收訊息、更新自己的狀態,並傳播給鄰居。 - **記憶體管理**: - 原本由 **primary** 統一管理的 **vertex queue** 和 **minimum distance array** 會被分散到每個 **worker** 內部。 - 邏輯上,仍然有工作池和距離陣列,但物理上,距離陣列的每個元素可能由不同的 **worker** 儲存,而每個 **worker** 也有自己的本地 **vertex queue**,只包含自己需要計算的任務。 - **worker** 只需要更新本地的 queue 和距離陣列值。 - 最後,需要將所有 **worker** 的結果收集起來,才能得到完整的最短路徑陣列。 - **工作池動態產生任務**:這個演算法的特殊之處在於,工作池中的任務 (task) 是在 **run-time** 根據計算過程動態產生的,而不是一開始就已知所有任務。當一個 **vertex** 的距離被更新,它就會被加入工作池中,等待後續處理。 --- 其他課程連結 [平行程式1C~2B Introduction parallel programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/Syxh3H7Kxe) [平行程式3A~3D The Latest Developments and Applications Using Parallel Programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJh7QFVKle) [平行程式4A~4B IO Parallel IO and Program Analysis](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJLMsuHFgg) [平行程式5A~5B The Latest Developments and Applications Using Parallel Programming](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/SJh57hIFle) [平行程式6A~6B Communication Routines and Parallel Function Code](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/r1X9kX_Fle) [平行程式 6C~6D Communication Routines and Parallel Function Code](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/S1DPjoYFlx) [平行程式 7A~8A Pthread:Synchronization Problem & Tools](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJu-_0tKge) [平行程式 8B~8D Synchronization Tools & Open Multi-Processing(OpenMP)](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1ki4E2Fee) [平行程式 9A~9B Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJTYMrpKlx) [平行程式 10A~10B Synchronization Tools & Open Multi-Processing Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/B1cY6M1qee) [平行程式 10C~10D Synchronization Tools & Open Multi-Processing Synchronization Construct](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BkgFaNg5gg) [平行程式 11A~11B Parallel Work Pool and Termination / Parallel Sorting](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1hfOw-5xl) [平行程式 12A~12B Parallel Sorting and Pipelined Computations](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/Symo-zQ9eg) [平行程式 12C~12D Parallel Sorting and Pipelined Computations](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJYNKDVceg) [平行程式 13A-13B Sychronous Parallelism](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HJ2UJ2Bqex) [平行程式 14A~14B Heterogeneous Computing](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BksS4yP5eg) [平行程式 14C~14D Heterogeneous Computing](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/BJrfTUd9xx) [平行程式 15A~15B Parallel Programming Model on GPU](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/ByWnl-t5gg) [平行程式 16A~16B What is Compute Unified Device Architecture(CUDA)?](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/HyYpsjcqgl) [平行程式 17A~18A 平行運算的CUDA](https://hackmd.io/@6FOC2dvARe-Vz0kVSyajew/H1dUeBT5lg) [平行程式 18B~19A 記憶體層級 / CUDA的優化](https://hackmd.io/@JuitingChen/HyF44e1jge) [平行程式 19B~19D 記憶體層級 / CUDA的優化 ](https://hackmd.io/@JuitingChen/ryPEu4lieg) [平行程式 20A~20B CUDA優化全域和區域記憶體/共享記憶體](https://hackmd.io/@JuitingChen/r1X659Zoxl) [平行程式 21A~21B Parallel Reduction / Distributed Computing Framework](https://hackmd.io/@JuitingChen/HyiOpozjxl) [平行程式 NTHU-PP-Chap10-Big Data-Part1 ](https://hackmd.io/@JuitingChen/Hyc-e3Golx) [平行程式 NTHU-PP-Chap10-Big Data-Part2 ](https://hackmd.io/@JuitingChen/ryC_QTXoxl) [平行程式 NTHU-PP-Chap11-MapReduce](https://hackmd.io/@JuitingChen/HJgBXJOsge) [平行程式 NTHU-PP-Chap12-Distributed Training-Part1](https://hackmd.io/@JuitingChen/ryh5hBtsge) [平行程式 NTHU-PP-Chap12-Distributed Training-Part2](https://hackmd.io/@JuitingChen/rJ2G7kdjxg) [平行程式 NTHU-PP-Chap12-Distributed Training-Part3](https://hackmd.io/@JuitingChen/HkA471dilx) [平行程式 NTHU-PP-Chap13-UCX-Part1](https://hackmd.io/@JuitingChen/rJbq103ieg) [平行程式 NTHU-PP-Chap13-UCX-Part2](https://hackmd.io/@JuitingChen/SJpNmk_ixl) [平行程式 NTHU-PP-Chap13-UCX-Part3](https://hackmd.io/@JuitingChen/HkIUYa13xe)