OpenMP为内联函数声明SIMD

OpenMP declare SIMD for an inline function

本文关键字:声明 SIMD 函数 OpenMP      更新时间:2023-10-16

当前的OpenMP标准介绍了C/C++的declare simd指令:

在函数上使用declare-simd构造可以创建可用于在SIMD循环中处理来自单个调用的多个参数同时

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

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

我这么问有两个原因:

  1. inline函数是一个非常不寻常的函数,因为它通常直接内联在被调用的地方。因此,它可能从未被编译为独立函数,因此,它的declare simd方面与封闭循环级别的可能的simd指令是相当冗余的
  2. 我有一个带有inline declare simd函数的代码,有时,由于一些模糊的原因,GCC会抱怨它们在链接时有多个定义(名称中有多余的字符,表明这些是矢量化版本)。但如果我删除declare simd指令,它会编译并链接良好

到目前为止,我还没有想太多,但现在我很困惑。这是我的错误吗(即对inline函数使用declare simd),还是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;
}

模拟立方厘米

#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的评论和Z玻色子的问题让我感到欣慰,因为我的代码符合OpenMP,这是GCC中的一个错误。我会用我能找到的最新版本做进一步的测试,并在需要时报告。

内联函数是一个非常不寻常的函数,因为它通常直接在被调用的地方内联。因此,它可能从未被编译为独立函数。

这是不正确的。除非声明为static,否则带或不带内联的函数都具有外部链接。编译器必须生成该函数的独立版本(不会内联),以防从另一个对象文件调用该函数。如果您不想要一个独立的函数,请声明函数static。有关更多详细信息,请参阅Agner Fog的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

CCD_ 20给出了相同的结果。

大写的"W"answers"T"表示符号是外部的。然而,"W"是不会引起链路错误的弱符号,而"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版本生成强符号,所以我不能完全回答你的问题,但我认为这些信息仍然很有趣。