对 OpenMP 上下文中的 firstprivate 和 threadprivate 感到困惑
Confused about firstprivate and threadprivate in OpenMP context
假设我在一个对象中打包了一些资源,然后根据这些资源进行一些计算。我通常做的是初始化并行区域外的对象,然后使用 firstprivte 关键字
int main()
{
// initialize Widget objs
Widget Widobj{params1,params2,params3...};
#pragma omp parallel for firstprivate(Widobj)
for (int i=0; i< N; ++i)
{
// computation based on resources in Widobj
}
}
而且我认为在这种情况下,每个线程都会独立处理 Widobj 中的资源,我想每个线程都会有一个 Widobj 的副本(可能是一个深层副本,对吗?)。现在我对另一个关键字 threadprivate 感到困惑,threadprivate 在这种情况下如何工作?在我看来他们很相似
当声明一个对象时firstprivate
,复制构造函数被调用,而当private
被使用时,默认构造函数被调用。我们将在下面解决 threadprivate
。证明(英特尔 C++ 15.0):
#include <iostream>
#include <omp.h>
class myclass {
int _n;
public:
myclass(int n) : _n(n) { std::cout << "int c'tor\n"; }
myclass() : _n(0) { std::cout << "def c'tor\n"; }
myclass(const myclass & other) : _n(other._n)
{ std::cout << "copy c'tor\n"; }
~myclass() { std::cout << "bye bye\n"; }
void print() { std::cout << _n << "\n"; }
void add(int t) { _n += t; }
};
myclass globalClass;
#pragma omp threadprivate (globalClass)
int main(int argc, char* argv[])
{
std::cout << "\nBegninning main()\n";
myclass inst(17);
std::cout << "\nEntering parallel region #0 (using firstprivate)\n";
#pragma omp parallel firstprivate(inst)
{
std::cout << "Hi\n";
}
std::cout << "\nEntering parallel region #1 (using private)\n";
#pragma omp parallel private(inst)
{
std::cout << "Hi\n";
}
std::cout << "\nEntering parallel region #2 (printing the value of "
"the global instance(s) and adding the thread number)\n";
#pragma omp parallel
{
globalClass.print();
globalClass.add(omp_get_thread_num());
}
std::cout << "\nEntering parallel region #3 (printing the global instance(s))\n";
#pragma omp parallel
{
globalClass.print();
}
std::cout << "\nAbout to leave main()\n";
return 0;
}
给予
def c'tor
Begninning main()
int c'tor
Entering parallel region #0 (using firstprivate)
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye bye
Entering parallel region #1 (using private)
def c'tor
Hi
bye bye
def c'tor
Hi
bye bye
def c'tor
Hi
bye bye
def c'tor
Hi
bye bye
Entering parallel region #2 (printing the value of the global instance(s) and adding the thread number)
def c'tor
0
def c'tor
0
def c'tor
0
0
Entering parallel region #3 (printing the global instance(s))
0
1
2
3
About to leave main()
bye bye
bye bye
如果复制构造函数执行深拷贝(如果你必须自己编写它应该这样做,如果你不这样做并且有动态分配的数据则默认执行),那么你会得到对象的深拷贝.这与不使用现有对象初始化私有副本的 private
相反。
threadprivate
的工作方式完全不同。首先,它仅适用于全局或静态变量。更重要的是,它本身就是一个指令,不支持任何其他子句。你在某处写了 threadprivate
pragma 行,然后在并行块之前写了 #pragma omp parallel
。还有其他差异(对象在内存中的存储位置等),但这是一个好的开始。
我们来分析一下上面的输出。
首先,请注意,在进入区域#2 时,调用默认构造函数创建线程专用的新全局变量。这是因为在进入第一个并行区域时,全局变量的并行副本尚不存在。
接下来,作为 NoseKnowsAll 认为最关键的区别,线程私有全局变量在不同的并行区域中是持久的。在区域 #3 中没有构造,我们看到从区域 #2 添加的 OMP 线程数被保留。另请注意,在区域 2 和 3 中没有调用析构函数,而是在离开 main()
之后(由于某种原因只有一个(主)副本 - 另一个是 inst
。这可能是一个错误.. .).
这让我们明白了我为什么使用英特尔编译器。 Visual Studio 2013 以及 g++(在我的电脑上是 4.6.2,Coliru (g++ v5.2), codingground (g++ v4.9.2)) allow only POD types (source)。这被列为近十年来的错误,但仍未得到完全解决。
给出的 Visual Studio 错误是
error C3057: 'globalClass' : dynamic initialization of 'threadprivate' symbols is not currently supported
而g++给出的错误是
error: 'globalClass' declared 'threadprivate' after first use
英特尔编译器适用于 类。
再补充一点。如果你想复制主线程变量的值你可以使用#pragma omp parallel copyin(globalVarName)
。请注意,这 不 与我们上面的示例中的 类 一起使用(因此我将其省略)。
假设我在一个对象中打包了一些资源,然后根据这些资源进行一些计算。我通常做的是初始化并行区域外的对象,然后使用 firstprivte 关键字
int main()
{
// initialize Widget objs
Widget Widobj{params1,params2,params3...};
#pragma omp parallel for firstprivate(Widobj)
for (int i=0; i< N; ++i)
{
// computation based on resources in Widobj
}
}
而且我认为在这种情况下,每个线程都会独立处理 Widobj 中的资源,我想每个线程都会有一个 Widobj 的副本(可能是一个深层副本,对吗?)。现在我对另一个关键字 threadprivate 感到困惑,threadprivate 在这种情况下如何工作?在我看来他们很相似
当声明一个对象时firstprivate
,复制构造函数被调用,而当private
被使用时,默认构造函数被调用。我们将在下面解决 threadprivate
。证明(英特尔 C++ 15.0):
#include <iostream>
#include <omp.h>
class myclass {
int _n;
public:
myclass(int n) : _n(n) { std::cout << "int c'tor\n"; }
myclass() : _n(0) { std::cout << "def c'tor\n"; }
myclass(const myclass & other) : _n(other._n)
{ std::cout << "copy c'tor\n"; }
~myclass() { std::cout << "bye bye\n"; }
void print() { std::cout << _n << "\n"; }
void add(int t) { _n += t; }
};
myclass globalClass;
#pragma omp threadprivate (globalClass)
int main(int argc, char* argv[])
{
std::cout << "\nBegninning main()\n";
myclass inst(17);
std::cout << "\nEntering parallel region #0 (using firstprivate)\n";
#pragma omp parallel firstprivate(inst)
{
std::cout << "Hi\n";
}
std::cout << "\nEntering parallel region #1 (using private)\n";
#pragma omp parallel private(inst)
{
std::cout << "Hi\n";
}
std::cout << "\nEntering parallel region #2 (printing the value of "
"the global instance(s) and adding the thread number)\n";
#pragma omp parallel
{
globalClass.print();
globalClass.add(omp_get_thread_num());
}
std::cout << "\nEntering parallel region #3 (printing the global instance(s))\n";
#pragma omp parallel
{
globalClass.print();
}
std::cout << "\nAbout to leave main()\n";
return 0;
}
给予
def c'tor
Begninning main()
int c'torEntering parallel region #0 (using firstprivate)
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye bye
copy c'tor
Hi
bye byeEntering parallel region #1 (using private)
def c'tor
Hi
bye bye
def c'tor
Hi
bye bye
def c'tor
Hi
bye bye
def c'tor
Hi
bye byeEntering parallel region #2 (printing the value of the global instance(s) and adding the thread number)
def c'tor
0
def c'tor
0
def c'tor
0
0Entering parallel region #3 (printing the global instance(s))
0
1
2
3About to leave main()
bye bye
bye bye
如果复制构造函数执行深拷贝(如果你必须自己编写它应该这样做,如果你不这样做并且有动态分配的数据则默认执行),那么你会得到对象的深拷贝.这与不使用现有对象初始化私有副本的 private
相反。
threadprivate
的工作方式完全不同。首先,它仅适用于全局或静态变量。更重要的是,它本身就是一个指令,不支持任何其他子句。你在某处写了 threadprivate
pragma 行,然后在并行块之前写了 #pragma omp parallel
。还有其他差异(对象在内存中的存储位置等),但这是一个好的开始。
我们来分析一下上面的输出。 首先,请注意,在进入区域#2 时,调用默认构造函数创建线程专用的新全局变量。这是因为在进入第一个并行区域时,全局变量的并行副本尚不存在。
接下来,作为 NoseKnowsAll 认为最关键的区别,线程私有全局变量在不同的并行区域中是持久的。在区域 #3 中没有构造,我们看到从区域 #2 添加的 OMP 线程数被保留。另请注意,在区域 2 和 3 中没有调用析构函数,而是在离开 main()
之后(由于某种原因只有一个(主)副本 - 另一个是 inst
。这可能是一个错误.. .).
这让我们明白了我为什么使用英特尔编译器。 Visual Studio 2013 以及 g++(在我的电脑上是 4.6.2,Coliru (g++ v5.2), codingground (g++ v4.9.2)) allow only POD types (source)。这被列为近十年来的错误,但仍未得到完全解决。 给出的 Visual Studio 错误是
error C3057: 'globalClass' : dynamic initialization of 'threadprivate' symbols is not currently supported
而g++给出的错误是
error: 'globalClass' declared 'threadprivate' after first use
英特尔编译器适用于 类。
再补充一点。如果你想复制主线程变量的值你可以使用#pragma omp parallel copyin(globalVarName)
。请注意,这 不 与我们上面的示例中的 类 一起使用(因此我将其省略)。