[英]Parallel for loop in openmp
我正在嘗試並行化一個非常簡單的 for 循環,但這是我很長時間以來第一次嘗試使用 openMP。 我對運行時間感到困惑。 這是我的代碼:
#include <vector>
#include <algorithm>
using namespace std;
int main ()
{
int n=400000, m=1000;
double x=0,y=0;
double s=0;
vector< double > shifts(n,0);
#pragma omp parallel for
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
cout << *std::max_element( shifts.begin(), shifts.end() ) << endl;
}
我編譯它
g++ -O3 testMP.cc -o testMP -I /opt/boost_1_48_0/include
也就是說,沒有“-fopenmp”,我得到了這些時間:
real 0m18.417s
user 0m18.357s
sys 0m0.004s
當我使用“-fopenmp”時,
g++ -O3 -fopenmp testMP.cc -o testMP -I /opt/boost_1_48_0/include
我得到了這些數字:
real 0m6.853s
user 0m52.007s
sys 0m0.008s
這對我來說沒有意義。 如何使用八核只能使性能提高 3 倍? 我是否正確編碼循環?
您應該對x
和y
使用 OpenMP reduction
條款:
#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
通過reduction
每個線程在x
和y
累積自己的部分和,最后將所有部分值相加以獲得最終值。
Serial version:
25.05s user 0.01s system 99% cpu 25.059 total
OpenMP version w/ OMP_NUM_THREADS=16:
24.76s user 0.02s system 1590% cpu 1.559 total
見 - 超線性加速:)
因為這個問題被高度關注,所以我決定添加一點 OpenMP 背景來幫助那些訪問它的人
#pragma omp parallel
使用一組threads
創建一個並行區域,其中每個線程執行parallel region
包含的整個代碼塊。 從OpenMP 5.1可以閱讀更正式的描述:
當線程遇到並行構造時,會創建一組線程來執行並行區域 (..)。 遇到並行構造的線程成為新組的主線程,在新並行區域的持續時間內線程編號為零。 新團隊中的所有線程,包括主線程,都執行該區域。 創建團隊后,團隊中的線程數在該並行區域的持續時間內保持不變。
#pragma omp parallel for
創建一個parallel region
(如前所述),並且該區域的threads
將使用default chunk size
和default schedule
(通常是static
分配它所包含的循環迭代。 但是請記住, default schedule
可能因OpenMP
標准的不同具體實現而異。
從OpenMP 5.1您可以閱讀更正式的描述:
工作共享循環結構指定一個或多個相關循環的迭代將由團隊中的線程在其隱式任務的上下文中並行執行。 迭代分布在執行工作共享循環區域綁定到的並行區域的團隊中已經存在的線程中。
此外,
並行循環結構是一種快捷方式,用於指定包含具有一個或多個關聯循環且沒有其他語句的循環結構的並行結構。
或者非正式地, #pragma omp parallel for
是構造函數#pragma omp parallel
與#pragma omp for
。 在您的情況下,這意味着:
#pragma omp parallel for
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
將創建一組線程,並將最外層循環的迭代塊分配給這些線程中的每一個。
為了使其更具說明性,使用4
線程, #pragma omp parallel for
chunk_size=1
和靜態schedule
將導致類似的結果:
在代碼方面,循環將轉換為邏輯上類似於:
for(int i=omp_get_thread_num(); i < n; i+=omp_get_num_threads())
{
c[i]=a[i]+b[i];
}
omp_get_thread_num 例程返回當前組內調用線程的線程號。
返回當前團隊中的線程數。 在程序的順序部分 omp_get_num_threads 返回 1。
或者換句話說, for(int i = THREAD_ID; i < n; i += TOTAL_THREADS)
。 THREAD_ID
范圍從0
到TOTAL_THREADS - 1
, TOTAL_THREADS
表示在並行區域上創建的團隊線程總數。
掌握了這些知識,並查看您的代碼,您可以看到變量“x”和“y”的更新存在競爭條件。 這些變量在線程之間共享並在並行區域內更新,即:
x += rand_g1;
y += rand_g2;
要解決此競爭條件,您可以使用 OpenMP 的縮減條款:
指定每個線程私有的一個或多個變量是並行區域末尾的歸約操作的主題。
非正式地,reduce 子句將為每個線程創建變量 'x' 和 'y' 的私有副本,並在並行區域的末尾將所有這些 'x' 和 'y' 變量求和到原始變量中來自初始線程的“x”和“y”變量。
#pragma omp parallel for reduction(+:x,y)
for (int j=0; j<n; j++) {
double r=0.0;
for (int i=0; i < m; i++){
double rand_g1 = cos(i/double(m));
double rand_g2 = sin(i/double(m));
x += rand_g1;
y += rand_g2;
r += sqrt(rand_g1*rand_g1 + rand_g2*rand_g2);
}
shifts[j] = r / m;
}
您最多可以實現(!)是線性加速。 現在我不記得哪個是來自 linux 的時間,但我建議您使用 time.h 或(在 c++ 11 中)“chrono”並直接從程序中測量運行時間。 最好將整個代碼打包成一個循環,運行 10 次,平均得到 prog 的大約運行時間。
此外,您在 imo 上遇到了 x,y 問題 - 這不符合並行編程中數據局部性的范式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.