並行計算:OpenMP(二)關於變量的可共享性——private、shared子句

一、private子句

1. private    

    private 子句可以將變量聲明爲線程私有,聲明稱線程私有變量以後,每個線程都有一個該變量的副本,線程之間不會互相影響,其他線程無法訪問其他線程的副本。原變量在並行部分不起任何作用,也不會受到並行部分內部操作的影響。

#include <stdio.h>
#include <omp.h>
int main(int argc, char* argv[])
{
	int i = 20;
	#pragma omp parallel for private(i)
	for (i = 0; i < 10; i++)
	{
		printf("i = %d\n", i);
	}
	printf("outside i = %d\n", i);
	return 0;
}
//運行結果:
i = 0
i = 2
i = 1
i = 4
i = 3
i = 5
i = 6
i = 7
i = 9
i = 8
outside i = 20

 2. firstprivate

    private子句不能繼承原變量的值,但是有時我們需要線程私有變量繼承原來變量的值,這樣我們就可以使用firstprivate子句來實現。

int main(int argc, char* argv[])
{
	int t = 20, i;
	#pragma omp parallel for firstprivate(t)
	for (i = 0; i < 5; i++)
	{
        //次數t被初始化爲20
		t += i;
		printf("t = %d\n", t);
	}
    //此時t=20
    printf("outside t = %d\n", t);
	return 0;
}

3. lastprivate

    除了在進入並行部分時需要繼承原變量的值外,有時我們還需要再退出並行部分時將計算結果賦值回原變量,lastprivate子句就可以實現這個需求。
    需要注意的是,根據OpenMP規範,在循環迭代中,是最後一次迭代的值賦值給原變量;如果是section結構,那麼是程序語法上的最後一個section語句賦值給原變量。
    如果是類(class)變量作爲lastprivate的參數時,我們需要一個缺省構造函數,除非該變量也作爲firstprivate子句的參數;此外還需要一個拷貝賦值操作符。

int main(int argc, char* argv[])
{
	int t = 20, i;
	// YOUR CODE HERE
	#pragma omp parallel for firstprivate(t), lastprivate(t)
	// END OF YOUR CODE
	for (i = 0; i < 5; i++)
	{
		t += i;
		printf("t = %d\n", t);
	}
	printf("outside t = %d\n", t);
	return 0;
}
===== OUTPUT =====
t = 20
t = 24
t = 23
t = 21
t = 22
outside t = 24

    需要注意的是,lastprivate必須要搭配firstprivate一起使用。

4. threadprivate

    threadprivate子句可以將一個變量複製一個私有的拷貝給各個線程,即各個線程具有各自私有的全局對象。

int g = 0;
#pragma omp threadprivate(g)

int main(int argc, char* argv[])
{
	int t = 20, i;
	#pragma omp parallel
	{
		g = omp_get_thread_num();
	}
	#pragma omp parallel
	{
		printf("thread id: %d g: %d\n", omp_get_thread_num(), g);
	}
	return 0;
}

    這裏threadprivate和private有什麼區別呢?private是對每一次並行任務都複製一份變量,當並行線程數大於等於並行任務的時候,就相當於給每一個線程都複製一個變量,跟threadprivate效果一樣,但是當線程數小於並行任務的時候,線程私有就不能達到private的效果,所以線程私有主要是針對一些面向線程的變量,而不是面向任務的變量。

二、shared子句 

   上面我們介紹了private子句,相對的,就有shread子句。Share子句可以將一個變量聲明成共享變量,並且在多個線程內共享。需要注意的是,在並行部分進行寫操作時,要求共享變量進行保護,否則不要隨便使用共享變量,儘量將共享變量轉換爲私有變量使用。

int main(int argc, char* argv[])
{
	int t = 20, i;
	#pragma omp parallel for shared(t)
	for (i = 0; i < 10; i++)
	{
		if (i % 2 == 0)
			t++;
		printf("i = %d, t = %d\n", i, t);
	}
	return 0;
}
===== OUTPUT =====
i = 8, t = 21
i = 1, t = 21
i = 0, t = 22
i = 4, t = 23
i = 6, t = 24
i = 3, t = 24
i = 5, t = 24
i = 7, t = 24
i = 9, t = 24
i = 2, t = 25

    這裏使用對t共享,實際上程序變成了串行。

三、OpenMP對private、share的隱式規則

    上面是通過private、shared子句顯式地指定數據在線程之間的共享性,當我們不明確指定的時候,規則又是怎麼樣的呢? 

    OpenMP有一組規則,可以推論出變量的數據共享屬性。

    例如,讓我們考慮以下代碼段:

int i = 0;
int n = 10;
int a = 7;

#pragma omp parallel for 
for (i = 0; i < n; i++)
{
    int b = a + i;
    ...
}

    有四個變量in, ab

    通常在並行區域之外聲明的變量的數據共享屬性。因此,n和 a是共享變量。

    但是,循環迭代變量默認情況下是私有的。因此, i是私有的。

    在並行區域內本地聲明的變量是私有的。因此b是私有的。

    我建議在並行區域內聲明循環迭代變量。在這種情況下,很顯然,此變量是私有的。上面的代碼片段如下所示:

int n = 10;                 // shared
int a = 7;                  // shared

#pragma omp parallel for 
for (int i = 0; i < n; i++) // i private
{
    int b = a + i;          // b private
    ...
}

四、默認的變量共享模式

    我們可以通過defaut()子句來指定所有變量的共享性,就不用一個一個去打了.

1.default(shared)  

 default子句有兩個版本。首先,我們關注default(shared)選項,然後考慮 default(none)條款。

    這兩個版本特定於OpenMP的C ++程序員。defaultFortran程序員還有其他可能性。

    該default(shared)子句將構造中所有變量的數據共享屬性設置爲共享。在下面的例子中

int a, b, c, n;
...

#pragma omp parallel for default(shared)
for (int i = 0; i < n; i++)
{
    // using a, b, c
}

 abc和 n被共享變量。

 default(shared)子句的另一種用法是指定大多數變量的數據共享屬性,然後另外定義私有變量。這種用法如下所示:

int a, b, c, n;

#pragma omp parallel for default(shared) private(a, b)
for (int i = 0; i < n; i++)
{
    // a and b are private variables
    // c and n are shared variables 
}

2.default(none) 

    該default(none)子句強制程序員明確指定所有變量的數據共享屬性。

int n = 10;
std::vector<int> vector(n);
int a = 10;

#pragma omp parallel for default(none) shared(n, vector, a)
for (int i = 0; i < n; i++)
{
    vector[i] = i * a;
}

3. 兩種模式對循環變量和在並行區域內聲明的變量的影響

    1. 循環變量和在並行區域內聲明的變量默認都是私有的 

    在default(shared)模式下,所有的並行區域外的變量都是shared的模式,對於循環變量和在並行區域內聲明的變量,不受該defautl(shared)模式的影響,還是private的,除非你對它們指定爲shared模式的;

    在default(none)情況下,它們同樣不受影響,該模式不會強制你對循環變量和並行區域內變量進行指定,但需要強制你對外部變量進行指定。比如下面,i不會要求強制指定,默認私有,但是不指定a就會報錯。

int i = 20; int a = 1;
#pragma omp parallel for default(none) private(a)
for (i = 0; i < 10; i++)
{
	printf("i = %d\t%d\n", i, a);
}
printf("outside i = %d\n", i);
return 0;

五、最佳編程實踐 

    程序員最好遵循以下兩個準則。

    第一條準則是始終使用default(none)子句編寫並行區域 。這迫使程序員明確考慮所有變量的數據共享屬性。

    第二條準則是在可能的情況下在並行區域內聲明私有變量。該準則提高了代碼的可讀性,並使代碼更清晰。

六、參考資料

【1】http://jakascorner.com/blog/2016/06/omp-data-sharing-attributes.html

 

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