簡體   English   中英

C ++和OpenMP-線程的執行時間大不相同

[英]C++ and OpenMP--vastly different execution times for threads

我在OMP線程方面遇到問題,因為並行節中的線程執行時間完全不同。 我在Linux群集上運行。 該代碼是純OpenMP代碼; 例如,沒有MPI代碼混在一起。它是用C ++編寫的。 我正在使用gcc 4.5.3,因此使用的是OpenMP 3.0。 我正在使用編譯優化級別2; 即-O2 我先給出代碼,然后給出從中生成的數據。 由於我想循環映射的鍵,因此我首先將鍵復制到向量vec_keys中,然后對vec_keys的元素進行並行for循環。 有一個與OMP並行化的for循環。 必須處理80萬個“節點”或條目。 並行for循環中有3個“命令”。

  1. 根據鍵獲取地圖迭代器。 參見以下行:node = vec_keys.at(itime);

  2. 使用步驟1中的迭代器,獲得指向C ++對象的指針,該對象將在上面調用方法。 參見行:p_nim = p_dmb-> getModel(node);

  3. 在步驟2中,對從地圖檢索到的對象調用方法。請參見以下語句:
    isStateUpdate = p_nim-> computeNextState(天,迭代,fsmId,p_dmb,tid,節點,p_np,p_ep,p_cp,p_fg,newStateNp,outStream);

請注意,在步驟2中,檢索到地圖條目,但未將其寫入。 在第3步中,地圖的內容已更改,但是是通過間接更改的。 即,地圖鍵不變。 值(在映射條目中)是指向在堆上實例化的基本數組的指針。 因此,通過不更改值指針,我可以更改基本數組的內容。 關鍵是我正在使用地圖,每個鍵在OMP for循環中被調用一次,並且沒有競爭條件或內存不一致。 我已經用1、2、4和8個線程運行了很多次,並且輸出始終是正確的。 每個映射鍵的上述步驟1和2中的操作都相同; 只有步驟3可以不同。 代碼是:

       #pragma omp parallel num_threads(numSpecOmpThreads) \
                private(itime,node,index,modelId,isStateUpdate,tid, \
                        b1time, \
                        e1time) \
                shared(numkeys,vec_keys,p_graph,p_cp,ifsm,p_np,p_dmb, \
                       p_ep,p_fg,newStateNp,day,iteration,fsmId, \
                       fakeMpiRankOpenMp,cout,outStream, \
                       startWtime,endWtime,counter, \
                       sinnertime,einnertime, \
                       dt1time, \
                       dt2time, \
                       dt3time, \
                       countChange) \
                default(none)
       {
           // Any variable in here is private.
           tid=omp_get_thread_num();
           NodeInteractModel02IF *p_nim=0;
           startWtime[tid] = omp_get_wtime();
           if (tid==0) {
               gettimeofday(&sinnertime,0);
           }

           #pragma omp for nowait
           for (itime=0; itime<numkeys; ++itime) {

               ++(counter[tid]);

               // node is a tail, or owned node.
               // This is step 1.
               gettimeofday(&b1time,0);
               node = vec_keys.at(itime);
               gettimeofday(&e1time,0);

               dt1time[tid] += (1000000  * (e1time.tv_sec - b1time.tv_sec) +
                        e1time.tv_usec - b1time.tv_usec);

               // This is step 2.
               gettimeofday(&b1time,0);
               p_nim = p_dmb->getModel(node);
               gettimeofday(&e1time,0);

               dt2time[tid] += (1000000  * (e1time.tv_sec - b1time.tv_sec) +
                        e1time.tv_usec - b1time.tv_usec);

               // This is step 3.
               gettimeofday(&b1time,0);
               isStateUpdate =  p_nim->computeNextState(lots of args);
               gettimeofday(&e1time,0);

               dt3time[tid] += (1000000  * (e1time.tv_sec - b1time.tv_sec) +
                        e1time.tv_usec - b1time.tv_usec);

               if (isStateUpdate) {
                   ++(countChange[tid]);
               }


           }  // End FOR over vector of owned nodes.


           endWtime[tid] = omp_get_wtime();

           if (tid==0) {
               gettimeofday(&einnertime,0);
           }

       }  // End pragma on OMP parallel.

現在,問題了。 以8線程執行為例。 執行結果如下所示。 這些是典型的。 dt1是運行上述第一步的累積時間(以秒為單位); dt2是運行上述第二步的累積時間; dt3是運行上述步驟3的累積時間。 cum = dt1 + dt2 + dt3。 countChange是在步驟3中更改的“節點”數的計數。有8行數據,每個線程一行(tid = 0是數據的第一行,…,tid = 7是最后一行)。 此運行中有800,000個“節點”,因此最多可以有8 x 100,000 = 800,000個countChanges。 我已經確認,每個線程正在處理總數800,000個節點中的100,000個。 因此,就每個線程而言,工作(就要處理的節點數而言)是相同的。 但是,如下所述,每個線程的計算量並不相同。

+++++++++++++++++++++++++++++

dt1 dt2 dt3暨(s)countChange

0.013292 0.041117 3.10149 3.1559 15

0.009705 0.041273 3.17969 3.23067 21

0.009907 0.040998 3.29188 3.34279 16

0.009905 0.040169 3.38807 3.43814 26

0.023467 0.039489 0.198228 0.261184 100000

0.023945 0.038114 0.187334 0.249393 100000

0.023648 0.042231 0.197294 0.263173 100000

0.021285 0.046682 0.219039 0.287006 100000

如預期,dt1小於dt2。 正如預期的那樣,兩者均小於dt3,因為步驟3涉及計算。 但是請注意,dt3值存在問題:它們的變化幅度超過一個數量級,並且分為兩類:一組具有dt3〜3.2,一組具有dt3〜0.19。 此外,執行速度最快的線程是工作最多的線程。 后四個線程中的每一個更改所有100,000個值,而前四個線程中的每個更改在15-26個值之間(顯然小於100,000個數量級)。 后四個線程執行更多的工作,因為更改節點時會有更多的計算。 此外,我正在運行的計算機是2節點,4核/節點的計算節點。 我希望主線程將是tid = 0,並且時間更短(如果有的話),但是它在具有更大時間的組中。 此外,單線程代碼產生的時間約為11.3秒。 現在,11.3 / 8 = 1.41秒。

由於代碼執行此循環以及其他類似的循環,因此執行了數百萬次,因此理想時間(1.41 s)與最大測量時間(上述3.44 s)之間的差異非常大,而且似乎過大。

此外,如果使用4個線程而不是8個線程運行上述示例,則前兩個線程的時間過多,而后兩個線程的時間很快。 請參閱以下4線程數據:

+++++++++++++++++++++++++++++

dt1 dt2 dt3暨(s)countChange

0.023794 0.073054 5.41201 5.50886 36

0.018677 0.072956 5.77536 5.86699 42

0.027368 0.066898 0.341455 0.435721 200000

0.026892 0.076005 0.363742 0.466639 200000

同樣,前兩個線程和最后兩個線程之間的差異在時間上是一個數量級(〜5.5對〜0.4); 同樣,運行速度最快的線程可以完成最多的工作。

這是示例2線程數據。 第二個線程完成了更多的工作-更改了40萬個節點,而第一個線程僅更改了78個節點-但運行速度提高了一個數量級(10.8對0.8)。 +++++++++++++++++++++++++++++

dt1 dt2 dt3暨(s)countChange

0.025298 0.144209 10.6269 10.7964 78

0.019307 0.126661 0.619432 0.7654 400000

我已經單獨使用OpenMP並結合使用OpenMP + MPI代碼多次重復了該實驗,並且每次都得到相同的結果(當然,這些值要花幾周的時間,但是趨勢相同)。 線程的前半部分(tid最小的那些線程)運行時間最長且工作較少。 同樣,使用gcc 4.7.0,因此使用OpenMP 3.1也會得到相同的結果。

對於這些線程為何在執行時間上有如此大的差異,以及如何消除這些差異,我將不勝感激。

首先,您真的確定花費較長時間的線程會減少工作量嗎? 因為奇怪的是,那些僅在少數幾個項目上起作用的線程總是花費最長時間。 如果是這種情況,您可以嘗試考慮以下因素:

  • 是否存在錯誤共享(幾個線程訪問同一高速緩存行,至少其中一些正在寫入)?
  • 您提到它在2節點計算機上運行。 您可以嘗試查看哪個節點執行哪個線程,以及在哪里分配內存。 除非節點之間的互連確實很糟,否則我懷疑這是問題所在。

盡管無法回答為什么某些線程會變慢的原因,但您可能希望嘗試對循環進行guideddynamic調度(例如, #pragma omp for nowait schedule(dynamic, 10000) ,當然,您希望chunk_size以獲得最大性能),以使更快的線程承擔更多負載,從而在線程之間更均勻地分配工作負載。

附帶說明一下:考慮到c ++允許在任何結構化塊內聲明變量,而在並行部分內聲明的變量始終是線程專有的,為什么您確實需要所有這些私有變量。 因此,在並行部分內部首次使用時聲明變量可能對可讀性甚至性能而言是個好主意(盡管在這種情況下不太可能)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM