OpenMp 任务:无法通过引用传递参数
OpenMp Task: can't pass argument by reference
g++ -fopenmp main.cpp
抱怨对 std::vector
的未定义引用。如何解决这个问题?
我已经在 Ubuntu 上安装了 libomp-dev
软件包。
main.cpp
#include<vector>
#include<iostream>
template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
T sum = 0;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(vec);
}
return vec[0];
}
int main() {
std::vector<int> a;
recursiveSumBody(a);
return 0;
}
未定义的引用
/tmp/ccTDECNm.o: In function `int recursiveSumBody<int, std::allocator<int> >(std::vector<int, std::allocator<int> >&) [clone ._omp_cpyfn.1]':
main.cpp:(.text+0x148): undefined reference to `std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&)'
collect2: error: ld returned 1 exit status
如果您也将 vec
声明为共享变量,问题就会消失:
#pragma omp task shared(sum, vec)
task
的默认可见性似乎是 firstprivate
,没有像预期的那样共享。您可以在 this forum entry.
中找到更多信息
要解决此问题,您可以手动指定 shared(sum, vec)
(强烈假设您希望共享)。
有趣的是,较旧的 gcc 版本(例如 5.4.0)提供了更有帮助的错误消息:
error: 'vec' implicitly determined as 'firstprivate' has reference type
而英特尔编译器 icpc 17.0.1
给出了“internal error : 0_1855
”。
手动指定 firstprivate
或 private
- 这对您的情况毫无意义 - 会导致其他更具描述性的错误。请注意,正如 Hristo Iliev 在其他评论中解释的那样,firstprivate
意味着为每个线程制作一个向量副本。
根据当前 (4.5) 标准:
In an orphaned task generating construct, if no default clause is present, formal arguments passed by reference are firstprivate
.
我想这适用于此。此外,
A variable that appears in a firstprivate
clause must not have an incomplete C/C++ type or be a reference to an incomplete type.
If a list item in a firstprivate
clause on a worksharing construct has a reference type then it must bind to the same object for all threads of the team.
它没有出现在一个子句中,但我认为这仍然是标准的意思。
现在我不认为 std::vector<T, A>
是模板中的不完整类型,除非我遗漏了有关模板如何实例化的信息。所以我确实认为你的代码应该是有效的,并且考虑到每个线程只绑定到同一个对象,它实际上是有意义的。
所以我认为这是最近 gcc
版本以及英特尔编译器中的错误。看起来编译器无法为模板实例化一些东西。
此外,添加:
if (0) std::vector<T, A> wtf = vec;
函数开头的使代码编译,link与gcc
。但是如果手动添加firstprivate
,gcc会继续报错'vec' has incomplete type
.
P.S.: Allowing reference types in data sharing attribute clauses was added in OpenMP 4.5, this is the old gcc gives a different error.
这看起来像是 GCC 中的错误,无法为 std::vector<int, std::allocator<int> >
生成复制构造函数。请注意,错误来自 linker 并且不会在编译阶段发生。复制构造函数用于初始化概述任务函数的 firstprivate
参数的复制函数。强制编译器生成它,例如改变
std::vector<int> a;
至
std::vector<int> a, b(a);
解决了问题。
这里有更详尽的描述。 GCC 转换如下代码
#pragma omp task shared(sum)
{
sum = recursiveSumBody(vec);
}
变成类似这样的东西:
struct omp_data_a data_o;
data_o.vec = vec;
data_o.sum = ∑
GOMP_task(omp_fn_0, &data_o, omp_cpyfn_1, 32, 8, 1, 0, 0, 0);
// --- outlined task body ---
void omp_fn_0(struct omp_data_s & restrict data_i)
{
struct vector & vec = &data_i->vec;
*data_i->sum = recursiveSumBody<int>(vec);
std::vector<int>::~vector(vec);
}
// --- task firstprivate initialisation function ---
void omp_cpyfn_1(struct omp_data_s *data_o, struct omp_data_a *data_i)
{
data_o->sum = data_i->sum;
struct vector &d40788 = data_i->vec;
struct vector *this = &data_o->vec;
std::vector<int>::vector(this, d40788); // <--- invocation of the copy constructor
}
omp_cpyfn_1
被 GOMP_task()
调用以初始化第一个私有参数。它调用了std::vector<int>
的复制构造函数,因为(first-)private
将对类型T的引用视为类型T本身,但并未生成构造函数,因此目标代码无法link。这可能是 gimplifier 代码中的一个错误,因为当非引用 std::vector<T, A>
被私有化时会创建复制构造函数,例如,使用如下代码:
...
std::vector<T, A> b;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(b);
}
...
代码使用 Intel 18.0b 编译。明确指定 vec
为 firstprivate
会以与 GCC 相同的方式破坏它(icpc 抱怨 vec
是不完整的类型)。可以使用以下解决方法:
template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
T sum = 0;
std::vector<T, A> *ptr = &vec;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(*ptr);
}
return vec[0];
}
在这种情况下 ptr
是一个指针。它的 firstprivate
版本是另一个指向相同位置的指针,即向量实例。语义不同于原始代码,因为这里没有创建整个向量的私有副本,而是使用原始向量。
g++ -fopenmp main.cpp
抱怨对 std::vector
的未定义引用。如何解决这个问题?
我已经在 Ubuntu 上安装了 libomp-dev
软件包。
main.cpp
#include<vector>
#include<iostream>
template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
T sum = 0;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(vec);
}
return vec[0];
}
int main() {
std::vector<int> a;
recursiveSumBody(a);
return 0;
}
未定义的引用
/tmp/ccTDECNm.o: In function `int recursiveSumBody<int, std::allocator<int> >(std::vector<int, std::allocator<int> >&) [clone ._omp_cpyfn.1]':
main.cpp:(.text+0x148): undefined reference to `std::vector<int, std::allocator<int> >::vector(std::vector<int, std::allocator<int> > const&)'
collect2: error: ld returned 1 exit status
如果您也将 vec
声明为共享变量,问题就会消失:
#pragma omp task shared(sum, vec)
task
的默认可见性似乎是 firstprivate
,没有像预期的那样共享。您可以在 this forum entry.
要解决此问题,您可以手动指定 shared(sum, vec)
(强烈假设您希望共享)。
有趣的是,较旧的 gcc 版本(例如 5.4.0)提供了更有帮助的错误消息:
error: 'vec' implicitly determined as 'firstprivate' has reference type
而英特尔编译器 icpc 17.0.1
给出了“internal error : 0_1855
”。
手动指定 firstprivate
或 private
- 这对您的情况毫无意义 - 会导致其他更具描述性的错误。请注意,正如 Hristo Iliev 在其他评论中解释的那样,firstprivate
意味着为每个线程制作一个向量副本。
根据当前 (4.5) 标准:
In an orphaned task generating construct, if no default clause is present, formal arguments passed by reference are
firstprivate
.
我想这适用于此。此外,
A variable that appears in a
firstprivate
clause must not have an incomplete C/C++ type or be a reference to an incomplete type. If a list item in afirstprivate
clause on a worksharing construct has a reference type then it must bind to the same object for all threads of the team.
它没有出现在一个子句中,但我认为这仍然是标准的意思。
现在我不认为 std::vector<T, A>
是模板中的不完整类型,除非我遗漏了有关模板如何实例化的信息。所以我确实认为你的代码应该是有效的,并且考虑到每个线程只绑定到同一个对象,它实际上是有意义的。
所以我认为这是最近 gcc
版本以及英特尔编译器中的错误。看起来编译器无法为模板实例化一些东西。
此外,添加:
if (0) std::vector<T, A> wtf = vec;
函数开头的使代码编译,link与gcc
。但是如果手动添加firstprivate
,gcc会继续报错'vec' has incomplete type
.
P.S.: Allowing reference types in data sharing attribute clauses was added in OpenMP 4.5, this is the old gcc gives a different error.
这看起来像是 GCC 中的错误,无法为 std::vector<int, std::allocator<int> >
生成复制构造函数。请注意,错误来自 linker 并且不会在编译阶段发生。复制构造函数用于初始化概述任务函数的 firstprivate
参数的复制函数。强制编译器生成它,例如改变
std::vector<int> a;
至
std::vector<int> a, b(a);
解决了问题。
这里有更详尽的描述。 GCC 转换如下代码
#pragma omp task shared(sum)
{
sum = recursiveSumBody(vec);
}
变成类似这样的东西:
struct omp_data_a data_o;
data_o.vec = vec;
data_o.sum = ∑
GOMP_task(omp_fn_0, &data_o, omp_cpyfn_1, 32, 8, 1, 0, 0, 0);
// --- outlined task body ---
void omp_fn_0(struct omp_data_s & restrict data_i)
{
struct vector & vec = &data_i->vec;
*data_i->sum = recursiveSumBody<int>(vec);
std::vector<int>::~vector(vec);
}
// --- task firstprivate initialisation function ---
void omp_cpyfn_1(struct omp_data_s *data_o, struct omp_data_a *data_i)
{
data_o->sum = data_i->sum;
struct vector &d40788 = data_i->vec;
struct vector *this = &data_o->vec;
std::vector<int>::vector(this, d40788); // <--- invocation of the copy constructor
}
omp_cpyfn_1
被 GOMP_task()
调用以初始化第一个私有参数。它调用了std::vector<int>
的复制构造函数,因为(first-)private
将对类型T的引用视为类型T本身,但并未生成构造函数,因此目标代码无法link。这可能是 gimplifier 代码中的一个错误,因为当非引用 std::vector<T, A>
被私有化时会创建复制构造函数,例如,使用如下代码:
...
std::vector<T, A> b;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(b);
}
...
代码使用 Intel 18.0b 编译。明确指定 vec
为 firstprivate
会以与 GCC 相同的方式破坏它(icpc 抱怨 vec
是不完整的类型)。可以使用以下解决方法:
template<typename T, typename A>
T recursiveSumBody(std::vector<T, A> &vec) {
T sum = 0;
std::vector<T, A> *ptr = &vec;
#pragma omp task shared(sum)
{
sum = recursiveSumBody(*ptr);
}
return vec[0];
}
在这种情况下 ptr
是一个指针。它的 firstprivate
版本是另一个指向相同位置的指针,即向量实例。语义不同于原始代码,因为这里没有创建整个向量的私有副本,而是使用原始向量。