【问题标题】:OpenMp Task: can't pass argument by referenceOpenMp 任务:无法通过引用传递参数
【发布时间】:2017-10-09 14:01:02
【问题描述】:

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

【问题讨论】:

  • 有人见过这样的吗?我想我可以使用指向向量第 0 个元素的指针,而不是 std::vector,但如果可能的话,我宁愿不直接使用指针。
  • 请注意,libomp-dev 是与gomp 无关的 LLVM OpenMP 运行时,gomp 是由gcc 捆绑的 OpenMP 运行时。

标签: c++ openmp undefined-reference


【解决方案1】:

如果您还将vec 声明为共享变量,问题就会消失:

#pragma omp task shared(sum, vec)

task 的默认可见性似乎是firstprivate,未按预期共享。您可以在this forum entry找到更多信息。

【讨论】:

  • 任务是可以(并且可能会)在未来时刻执行的代码片段。由于通常期望它们适用于相同变量中提供的不同输入值集,因此更自然地期望它们是firstprivate 而不是shared。想想异步闭包。
  • @HristoIliev 我发现firstprivate 的语义在引用上并不直观(想想auto firstprivate = reference)。你同意firstprivateshared 之间没有语义上的区别吗?
  • @Zulan,引用是一些内存对象的替代名称,而不是指针,因此firstprivate,遵循private 的语义,创建整个对象的副本,而不仅仅是对它的另一个引用.
  • @HristoIliev 我可能读错了 "如果工作共享结构的 firstprivate 子句中的列表项具有引用类型,那么它必须绑定到团队所有线程的同一个对象。”。对于标准中不同的数据共享条款如何处理引用,我找不到任何其他好的具体描述。
  • @Zulan "如果列表项的类型是对类型 T 的引用,那么在本条款的所有目的中,该类型都将被视为 T" (p .194、private 子句)和“出现在firstprivate 子句中的列表项是private 子句语义的主题...,除非另有说明”(p.197 ,firstprivate 子句)。另请注意,task 不是工作共享结构。
【解决方案2】:

要解决此问题,您可以手动指定 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 在其他 cmets 中解释的那样,firstprivate 意味着为每个线程制作一个向量副本。

按照当前 (4.5) 标准:

在孤立的任务生成构造中,如果不存在默认子句,则通过引用传递的正式参数为 firstprivate

我想这适用于这里。此外,

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

它没有出现在子句中,但我认为这仍然是标准的意思。

现在我不认为std::vector&lt;T, A&gt; 是模板中的不完整类型,除非我遗漏了有关如何实例化模板的内容。所以我认为你的代码应该是有效的,并且考虑到每个线程只是绑定到同一个对象,它实际上是有意义的。

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

进一步补充:

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

在函数的开头使代码编译并链接到gcc。但是如果手动添加firstprivate,gcc会继续抱怨'vec' has incomplete type

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

【讨论】:

  • Intel 18.0b 抱怨 vec 是不完整或引用类型,如果在 firstprivate 子句中明确指定,否则编译没有问题。由于无限递归,程序运行时崩溃。
【解决方案3】:

这看起来像是 GCC 中的一个错误,它无法为 std::vector&lt;int, std::allocator&lt;int&gt; &gt; 生成复制构造函数。请注意,错误来自链接器,并且不会在编译阶段发生。复制构造函数用于初始化概述任务函数的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&lt;int&gt; 的复制构造函数,因为(first-)private 将类型T 的引用视为类型T 本身,但没有生成构造函数,因此目标代码链接失败。这可能是 gimplifier 代码中的一个错误,因为当非引用 std::vector&lt;T, A&gt; 私有化时创建了复制构造函数,例如,使用如下代码:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-17
    • 2012-11-04
    • 1970-01-01
    • 2012-10-17
    • 2010-10-07
    • 2011-08-21
    相关资源
    最近更新 更多