OpenMP声明SIMD用于内联函数

时间:2015-12-04 15:08:11

标签: c++ openmp

current OpenMP standard说的是关于C / C ++的declare simd指令:

  

在函数上使用declare simd构造可以创建   可用于相关功能的SIMD版本   处理SIMD循环中单个调用的多个参数   同时进行。

本章给出了更多细节,但似乎对该指令可以应用的函数类型没有限制。

所以我的问题是,这个指令可以安全地应用于inline函数吗?

我问这有两个原因:

  1. inline函数是一个相当不寻常的函数,因为它通常直接在调用它的位置内联。因此,它可能永远不会编译为独立函数,因此,它的declare simd方面与封闭循环级别的可能simd指令相当多余。
  2. 我有一个带有inline declare simd个函数的代码,有时候,由于一些模糊的原因,GCC抱怨他们在链接时的多重定义(名称被额外的字符修改,表明这些是矢量化的版本)。但是如果我删除declare simd指令,它就会编译并链接正常。
  3. 到目前为止,我没有想太多,但现在我很困惑。这是我的错误(即declare simd使用inline函数)还是GCC生成inline函数的二进制矢量化版本并且在链接时无法对其进行排序的问题?

    修改
    有一个GCC编译器选项可以带来改变。启用内联时(例如-O3),代码编译并链接正常。但是当使用-O0-O3 -fno-inline进行编译时,内联将被禁用,并且链接将失败,并使用omp declare simd指令修饰的函数的“多重定义”。

    编辑2:
    感谢@Zboson关于编译器标志的问题,我设法创建了一个复制器。这是:

    foobar.h中

    #ifndef FOOBAR_H_
    #define FOOBAR_H_
    
    #include <cmath>
    
    #pragma omp declare simd
    inline double foo( double d ) {
        return sin( cos( exp( d ) ) );
    }
    
    double bar( double *v, int len );
    
    #endif
    

    foobar.cc

    #include "foobar.h"
    
    double bar( double *v, int len ) {
        double sum = 0;
        for ( int i = 0; i < len; i++ ) {
            sum += foo( v[i] );
        }
        return sum;
    }
    

    simd.cc

    #include <iostream>
    #include "foobar.h"
    
    int main() {
    
        const int len = 100;
        double *v = new double[len];
    
        for ( int i = 0; i < len; i++ ) {
            v[i] = i;
        }
    
        double sum = 0;
        #pragma omp simd reduction( +: sum )
        for ( int i = 0; i < len; i++ ) {
            sum += foo( v[i] );
        }
    
        std::cout << sum << "  " << bar( v, len ) << std::endl;
    
        delete[] v;
    
        return 0;
    }
    

    汇编

    > g++ -fopenmp -g simd.cc foobar.cc
    /tmp/ccI4e7ip.o: In function `_ZGVbN2v__Z3food':
    foobar.h:7: multiple definition of `_ZGVbN2v__Z3food'
    /tmp/cc4U8Qyu.o:foobar.h:7: first defined here
    /tmp/ccI4e7ip.o: In function `_ZGVbM2v__Z3food':
    foobar.h:7: multiple definition of `_ZGVbM2v__Z3food'
    /tmp/cc4U8Qyu.o:foobar.h:7: first defined here
    /tmp/ccI4e7ip.o: In function `_ZGVcN4v__Z3food':
    foobar.h:7: multiple definition of `_ZGVcN4v__Z3food'
    /tmp/cc4U8Qyu.o:foobar.h:7: first defined here
    /tmp/ccI4e7ip.o: In function `_ZGVcM4v__Z3food':
    foobar.h:7: multiple definition of `_ZGVcM4v__Z3food'
    foobar.h:7: first defined here
    /tmp/ccI4e7ip.o: In function `_ZGVdN4v__Z3food':
    foobar.h:7: multiple definition of `_ZGVdN4v__Z3food'
    foobar.h:7: first defined here
    /tmp/ccI4e7ip.o: In function `_ZGVdM4v__Z3food':
    foobar.h:7: multiple definition of `_ZGVdM4v__Z3food'
    foobar.h:7: first defined here
    collect2: error: ld returned 1 exit status
    > c++filt _ZGVdM4v__Z3food
    _ZGVdM4v__Z3food
    > c++filt _Z3food
    foo(double)
    

    Gcc版本4.9.2和5.1.0都给出了同样的问题,而英特尔编译器版本15.0.3编译得很好。

    最终修改
    Hristo Iliev's commentZ boson's question让我感到安慰,因为我的代码符合OpenMP标准,而且这是GCC中的一个错误。我会看到使用我能找到的最新版本进行进一步测试,并在需要时报告。

1 个答案:

答案 0 :(得分:1)

  

内联函数是一个相当不寻常的函数,因为它通常直接在调用它的位置内联。因此它很可能永远不会编译为独立函数。

这是不正确的。除非声明static具有外部链接,否则包含或不包含内联的函数。如果从另一个目标文件调用该函数,编译器必须生成该函数的独立版本(不会内联)。如果您不希望独立函数声明函数static。有关更多详细信息,请参阅Agner Fog的Optimizing software in C++中的第8.3节和“内联函数具有非内联副本”标题。

使用static inline double foo不会导致代码出错。

现在让我们看看这些符号。不使用static

nm foobar.o | grep foo

给出

W _Z3food
T _ZGVbM2v__Z3food
T _ZGVbN2v__Z3food
T _ZGVcM4v__Z3food
T _ZGVcN4v__Z3food
T _ZGVdM4v__Z3food
T _ZGVdN4v__Z3food

nm foobar.o | grep foo给出了相同的东西。

大写的“W”和“T”表示符号是外部的。然而,“W”是weak symbol,它不会导致链接错误,但“T”是强符号。这就说明了链接器抱怨的原因。

static inline的结果是什么?在这种情况下nm foobar.o | grep foo给出

t _ZGVbM2v__ZL3food
t _ZGVbN2v__ZL3food
t _ZL3food

和nm simd.o | grep foo给出了同样的东西。但是小写“t”表示符号具有局部链接,因此链接器没有问题。

如果我们在没有OpenMP的情况下编译,则生成的唯一foo符号为_ZL3food。我不知道为什么GCC会为非SIMD版本的功能和SIMD版本的强符号生成弱符号,所以我不能完全回答你的问题,但我认为这些信息仍然很有趣。