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”。

手动指定 firstprivateprivate - 这对您的情况毫无意义 - 会导致其他更具描述性的错误。请注意,正如 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 = &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_1GOMP_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 编译。明确指定 vecfirstprivate 会以与 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 版本是另一个指向相同位置的指针,即向量实例。语义不同于原始代码,因为这里没有创建整个向量的私有副本,而是使用原始向量。