MPI進程餓死問題

在編寫並行計算的程序時,一定要注意“進程餓死”的問題,剛接觸MPI並行計算的同學可能不太注意這個問題,也很容易寫出這樣的程序。那麼何爲餓死呢,簡單講述一下:

並行計算大佬劉文志“風辰”在《並行算法設計與性能優化》一書中給出了“餓死”幾個定義(書的P140處):

所謂“餓死”是指某個控制流一直得不到計算,本質上是一種負載不均衡的問題。大多數情況下可能和優先級有關係,另外就是軟件開發人員的失誤。餓死的直觀表現就是控制流一直在等待,就好像進入了死循環。比如系統已優先級調度控制流,軟件開發人員錯誤滴爲某個控制流分配了一個非常低的優先級,那麼可能只要系統有任務並行,這個控制流就不可能得到處理器。另外一種餓死是從任務的角度來說,也即某個任務一直存在系統中,沒有控制流來計算它。

我遇到的“餓死”是任務級別的,和風辰說的第二種情況類似,但是又不完全一樣,是在MPI並行計算中遇到的問題。我用MPI做地震勘探中的多炮並行數值模擬,即任務級別的並行。當發4個MPI進程算7炮地震數據時,就會發生負載不均衡的問題。前三個進程都可以計算2炮,第四個進程只能計算1炮。

爲了使進程同步,我們一般用MPI_Barrier(comm)語句強制所有進程在某個地方等待,這裏Barrier的設置很關鍵,搞不好就會出現餓死的問題,我們看兩段簡單的代碼:

有餓死問題:

        /* loop shot by shot */
        for (is=s_beg;is<s_end;is++)
        {       
            /****************** caculate forward wavefield  *******************/           
            wavefield_forward(nx,ny,nz);

            /****************** caculate residual wavefield  ******************/
            Com_Resi(nx,ny,nz);

            /****************** caculate backward wavefield  ******************/           
            wavefield_backward(nx,ny,nz);
            MPI_Barrier(comm);

            /********************** caculate gradient *********************/           
            Com_Grad(nx,ny,nz);
            MPI_Barrier(comm);

            /* collect the gradient on this processor */
            for (ixyz=0;ixyz<nxyz;ixyz++)
            {
                h_gradV[ixyz] += h_gradVT[ixyz];
            }
            MPI_Barrier(comm);    		
        }/* end shot loops */
        MPI_Barrier(comm);

當is=0時,每個進程都有任務可以計算,大家基本同步進行,此時MPI_Barrier不會出問題。

當is=1時,進程1~3還有第二炮可以算,進程4卻沒得任務可以算了,當遇到MPI_Barrier的時候,進程1~3就會等待進程4前來匯合,然後同步往下走,但進程4已經結束炮的計算轉入下一階段的任務處理。不幸的是,進程1~3不知道進程4已經走到下一站,在遇到MPI_Barrier還在癡癡地等待。事實上即便等上一萬年,進程1~3都等不到進程4在這一環節上來匯合了,這就是發生了餓死的問題。

無餓死問題:

        /* loop shot by shot */
        for (is=s_beg;is<s_end;is++)
        {       
            /****************** caculate forward wavefield  *******************/           
            wavefield_forward(nx,ny,nz);

            /****************** caculate residual wavefield  ******************/
            Com_Resi(nx,ny,nz);

            /****************** caculate backward wavefield  ******************/           
            wavefield_backward(nx,ny,nz);
            // MPI_Barrier(comm);

            /********************** caculate gradient *********************/           
            Com_Grad(nx,ny,nz);
            // MPI_Barrier(comm);

            /* collect the gradient on this processor */
            for (ixyz=0;ixyz<nxyz;ixyz++)
            {
                h_gradV[ixyz] += h_gradVT[ixyz];
            }
            // MPI_Barrier(comm);    		
        }/* end shot loops */
        MPI_Barrier(comm);

這裏,我們把循環中的MPI_Barrier都註銷掉。即讓這四個進程獨立地計算任務,相互之間不用等待。這樣進程1~3在算第二炮時就不會再癡癡地等待進程4。在循環結束後設置MPI_Barrier,意在告訴這四個進程在循環計算的過程中可以獨立地行計算,但在循環計算結束後,必須停下來等待,等到4個進程都跑到這裏時再轉入下一階段的計算。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章