OpenMP 为内联函数声明 SIMD

Posted

技术标签:

【中文标题】OpenMP 为内联函数声明 SIMD【英文标题】:OpenMP declare SIMD for an inline function 【发布时间】:2015-12-04 15:08:11 【问题描述】:

current OpenMP standard 表示 C/C++ 的 declare simd 指令:

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

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

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

我问这个有两个原因:

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

到目前为止,我还没有想太多,但现在我很困惑。这是我的一个错误(即使用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 都给出了同样的问题,而 Intel 编译器版本 15.0.3 编译它就好了。


最终编辑:Hristo Iliev's comment 和 Z boson's question 安慰我,我的代码符合 OpenMP,这是 GCC 中的一个错误。我会用我能找到的最新版本进行进一步测试,并在需要时报告。

【问题讨论】:

“所以它可能永远不会编译为独立函数......”不太确定。 失败时使用了哪些编译选项? 使用-O2 而不是-O3-Ofast 会发生什么? 嗯。好吧,我的想法就这么多,但这仍然很有趣。也许这与 omp simd-Ofast 启用的新 SIMD 数学函数有关? @VladimirF 是的,我也不确定。进一步的测试表明,确实,问题出在函数没有被内联。 【参考方案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 版本生成强符号,所以我无法完全回答您的问题,但我认为这些信息会很有趣。

【讨论】:

内联函数必须在使用它们的每个编译单元中定义。由于它们也必须在行外发出,因此在多个编译单元中会出现重复符号。解决方案是使符号变弱。在我看来,GCC SIMD-iser 做得很草率,并没有将离线矢量化版本标记为弱。可能是编译器错误或未定义的行为。 @HristoIliev,我同意它看起来可能是一个错误。如果你有兴趣这个问题启发my own question。顺便说一句,OPs 示例代码在启用优化或使用static inline 时编译得很好。 @HristoIliev 和 Z boson,我认为这几乎回答了我的主要问题,即我的代码是否符合标准。所以对我来说,这是一个 GCC 错误,我会看到报告它。谢谢大家。 @Gilles,谢谢你的提问!我实际上消除了我对 inline 的一些误解(尽管我还有一些东西要学习)并且由于你的问题现在更好地理解了 C++。 我猜,我们都做到了:)

以上是关于OpenMP 为内联函数声明 SIMD的主要内容,如果未能解决你的问题,请参考以下文章

内联函数

C++ 内联函数:这样声明,这样定义,还是两者兼而有之?为啥?

7 类-内联函数

内联函数分析

内联函数

在基类体内声明但通过派生类调用的函数的内联