簡體   English   中英

OpenMP和C ++並行for循環:為什么我的代碼在使用OpenMP時會變慢?

[英]OpenMP and C++ parallel for loop: why does my code slow down when using OpenMP?

我有一個關於使用OpenMP(使用C ++)的簡單問題,我希望有人可以幫助我。 我在下面添加了一個小例子來說明我的問題。

#include<iostream>
#include<vector>
#include<ctime>
#include<omp.h>

using namespace std;

int main(){
  srand(time(NULL));//Seed random number generator                                                                               

  vector<int>v;//Create vector to hold random numbers in interval [0,9]                                                                                   
  vector<int>d(10,0);//Vector to hold counts of each integer initialized to 0                                                                    

  for(int i=0;i<1e9;++i)
    v.push_back(rand()%10);//Push back random numbers [0,9]                                                                      

  clock_t c=clock();

  #pragma omp parallel for
  for(int i=0;i<v.size();++i)
    d[v[i]]+=1;//Count number stored at v[i]                                                                                     

  cout<<"Seconds: "<<(clock()-c)/CLOCKS_PER_SEC<<endl;

  for(vector<int>::iterator i=d.begin();i!=d.end();++i)
  cout<<*i<<endl;

  return 0;
}

上面的代碼創建了一個向量v ,它包含[0,9]范圍內的10億個隨機整數。 然后,代碼循環通過v計算每個不同整數的實例數(即,在v中找到多少個,有多少兩個,等等)

每次遇到特定整數時,通過遞增向量d的適當元素來計算它。 因此, d[0]計算多少個零, d[6]計算多少個六,依此類推。 到目前為止有道理嗎?

我的問題是當我嘗試使計數循環並行時。 如果沒有#pragma OpenMP語句,我的代碼需要20秒,但是使用pragma需要60秒。

很明顯,我誤解了一些與OpenMP相關的概念(也許是如何共享/訪問數據的?)。 有人可以解釋我的錯誤,或者指點我用一些有見識的文獻和適當的關鍵詞來幫我搜索?

你的代碼exibits:

  • 由於未經同步訪問共享變量而導致的競爭條件
  • 錯誤和真實的共享緩存問題
  • 錯誤的運行時間測量

競爭條件的出現是因為您在多個線程中同時更新向量d的相同元素。 注釋掉srand()行並使用相同數量的線程(但具有多個線程)多次運行代碼。 比較不同運行的輸出。

當兩個線程寫入彼此接近的內存位置以產生相同的高速緩存行時,就會發生錯誤共享。 這導致高速緩存行在多串口系統中不斷地從核心跳轉到核心或CPU到CPU,以及過多的高速緩存一致性消息。 每個高速緩存行有32個字節,向量的8個元素可以放在一個高速緩存行中。 每個高速緩存行有64個字節,整個向量d適合一個高速緩存行。 這使得Core 2處理器上的代碼變慢,而Nehalem和后Nehalem(例如Sandy Bridge)上的代碼稍慢(但不像Core 2那么慢)。 真正的共享發生在兩個或多個線程同時訪問的元素上。 您應該將增量放在OpenMP atomic構造中(慢),使用OpenMP鎖定數組來保護對d元素的訪問(更快或更慢,取決於您的OpenMP運行時)或累積本地值,然后執行最終的同步減少(最快的)。 第一個實現如下:

#pragma omp parallel for
for(int i=0;i<v.size();++i)
  #pragma omp atomic
  d[v[i]]+=1;//Count number stored at v[i]   

第二個實現如下:

omp_lock_t locks[10];
for (int i = 0; i < 10; i++)
  omp_init_lock(&locks[i]);

#pragma omp parallel for
for(int i=0;i<v.size();++i)
{
  int vv = v[i];
  omp_set_lock(&locks[vv]);
  d[vv]+=1;//Count number stored at v[i]
  omp_unset_lock(&locks[vv]);
}

for (int i = 0; i < 10; i++)
  omp_destroy_lock(&locks[i]);

(包括omp.h以訪問omp_*函數)

我讓你想出第三個選項的實現。

您正在使用clock()測量已用時間,它會測量CPU時間,而不是運行時間。 如果一個線程以100%的CPU使用率運行1秒鍾,則clock()會指示CPU時間增加1秒。 如果您有8個線程以100%CPU使用率運行1秒鍾,則clock()將指示CPU時間增加8秒(即8個線程乘以每個線程1個CPU秒)。 請改用omp_get_wtime()gettimeofday() (或其他一些高分辨率計時器API)。

編輯一旦通過正確的同步解決了競爭條件,則適用以下段落,在此之前您的數據競爭條件不幸地使速度比較靜音:

您的程序正在變慢,因為在pragma部分中有10個可能的輸出隨機訪問。 沒有鎖定(您需要通過同步提供),OpenMP無法訪問任何這些元素,並且鎖定將導致您的線程具有比並行計數更高的開銷。

提高速度的一個解決方案是為每個OpenMP線程創建一個局部變量,該局部變量計算特定線程所見的所有0-10值。 然后在主計數向量中對它們求和。 由於線程不需要鎖定共享寫向量,因此這將很容易並行化並且更快。 我希望接近Nx加速,其中N是來自OpenMP的線程數,因為需要非常有限的鎖定。 此解決方案還避免了代碼中當前的許多競爭條件。

有關線程本地OpenMP的更多詳細信息,請參見http://software.intel.com/en-us/articles/use-thread-local-storage-to-reduce-synchronization/

暫無
暫無

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

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