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的主要内容,如果未能解决你的问题,请参考以下文章