2017-05-10 26 views
5

g++ -fopenmp main.cpp抱怨未定义的引用为std::vector。如何解决这个问题?OpenMp任务:无法通过引用传递参数

我在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 
+0

任何人都看过类似的东西?我想我可以使用指向矢量的第0个元素的指针,而不是'std :: vector',但如果可能的话,我宁愿不直接使用指针。 –

+0

请注意,'libomp-dev'是与'gomp'无关的LLVM OpenMP运行时,它是'gcc'捆绑的OpenMP运行时。 – Zulan

回答

3

要解决该问题,您可以手动指定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)标准:

在一个孤立的任务产生构建体,如果没有默认子句,通过引用传递的形式参数是firstprivate

我想这适用于此。此外,

出现在firstprivate子句中的变量不能具有不完整的C/C++类型,或者是对不完整类型的引用。 如果工作共享构造上的firstprivate子句中的列表项具有引用类型,那么它必须绑定到团队所有线程的同一对象。

它没有出现在一个条款中,但我认为这仍然是标准的含义。

现在我不认为std::vector<T, A>是模板内的不完整的类型,除非我失去了一些东西有关模板是如何被实例化。所以我认为你的代码应该是有效的,并且假设每个线程都绑定到同一个对象上,它实际上是有意义的。

所以我认为这是最近gcc版本以及英特尔编译器中的一个错误。它看起来像编译器无法实例化模板的一些东西。

此外,加入:

if (0) std::vector<T, A> wtf = vec; 

在函数开始时使代码编译和链接与gcc。但如果手动添加firstprivate,gcc会继续抱怨'vec' has incomplete type

P.S .:允许在数据共享属性子句中引用类型在OpenMP 4.5中添加,这是旧的gcc给出了不同的错误。

+0

Intel 18.0b抱怨说,如果在'firstprivate'子句中明确指定'vec'是一个不完整或引用类型,否则编译时没有问题。由于无限递归,程序运行时崩溃。 –

1

问题消失,如果你还声明vec作为共享变量:

#pragma omp task shared(sum, vec) 

似乎默认的曝光率taskfirstprivate,不会像预期的那样分享。你可以在this forum entry找到更多的信息。

+0

任务是可能(也可能会)在未来一段时间执行的代码片段。由于这些通常被期望在相同变量中提供的不同输入值集合上工作,因此期望这些输入值是“firstprivate”而不是“shared”。考虑异步封闭。 –

+0

@HristoIliev我发现引用'firstprivate'的语义不直观直观(想'auto firstprivate = reference')。你是否同意'firstprivate'和'shared'之​​间没有语义上的区别? – Zulan

+0

@Zulan,引用是一些内存对象的替代名称,而不是指针,因此'firstprivate'跟在'private'的语义之后,创建整个对象的一个​​副本,而不是简单地引用它。 –

1

这看起来像GCC中的错误,它无法生成std::vector<int, std::allocator<int> >的复制构造函数。请注意,错误来自链接器,并且在编译阶段不会发生。复制构造函数用于初始化概述的任务函数的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_1得到由GOMP_task()以初始化FIRSTPRIVATE参数调用。它调用std::vector<int>的拷贝构造函数,因为(first-)private将类型T的引用视为类型T本身,但构造函数未生成,因此目标代码无法链接。这可能是在gimplifier将代码中的错误当非参考std::vector<T, A>被私有化的拷贝构造函数被创建,例如,用这样的代码:

... 
std::vector<T, A> b; 
#pragma omp task shared(sum) 
{ 
    sum = recursiveSumBody(b); 
} 
... 

代码与英特尔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版本是指向相同位置的另一指针,即矢量实例。语义不同于原始代码,因为这里没有创建整个向量的私人副本,而是使用原始向量。