OpenMP 为内联函数声明 SIMD

OpenMP declare SIMD for an inline function

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

The use of a declare simd construct on a function enables the creation of SIMD versions of the associated function that can be used to process multiple arguments from a single invocation in a SIMD loop concurrently.

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

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

我问这个有两个原因:

  1. inline 函数是一个相当不寻常的函数,因为它通常直接内联在调用它的地方。所以它可能永远不会被编译为一个独立的函数,因此,它的 declare simd 方面对于封闭循环级别的可能 simd 指令是非常多余的。
  2. 我有一个带有 inline declare simd 函数的代码,有时,出于某些模糊的原因,GCC 在 link 时抱怨它们的多重定义(名称被额外的破坏暗示这些是矢量化版本的字符)。但是如果我删除 declare simd 指令,它会编译并且 link 没问题。

到目前为止我还没有想太多,但现在我很困惑。这是我的错误(即对 inline 函数使用 declare simd )还是 GCC 生成 inline 函数的二进制矢量化版本并且未能在 [=89 处对它们进行排序时出现问题=]时间?


编辑:
有一个 GCC 编译器选项有所不同。当启用内联时(例如 -O3),代码编译并且 link 没问题。但是当使用 -O0-O3 -fno-inline 编译时,内联被禁用并且 linking 失败,这个 "multiple definition of" 用 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 编译它就好了。


最终编辑:
and 安慰我一下,我的代码是 OpenMP 兼容的,这是 GCC 中的一个错误。我会使用我能找到的最新版本进行进一步测试,并在需要时报告。

An inline function is a rather unusual function, since it is normally inlined directly in the place it was called. So it is likely never compiled as a standalone function.

这是不正确的。除非声明为 static,否则带或不带内联的函数具有外部 linkage。编译器必须生成函数的 stand-alone 版本(不会内联),以防从另一个 object 文件调用该函数。如果您不想要独立函数,请声明函数 static。有关详细信息,请参阅 Agner Fog Optimizing software in C++ 中的第 8.3 节和标题 "Inlined functions have a non-inlined copy"。

使用 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,它不会导致 link 错误,但是 "T" 是一个强符号,它会导致错误。所以这就是 linker 抱怨的原因。

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

t _ZGVbM2v__ZL3food
t _ZGVbN2v__ZL3food
t _ZL3food

和 nm simd.o | grep foo 给出了同样的东西。但是小写 "t" 表示符号具有本地 linkage,因此 linker.

没有问题

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