指定编译器可以使用的函数的 simd 级别

Jim*_*mbo 4 c gcc simd

我编写了一些代码,并使用 gcc 和本机架构选项对其进行了编译。

通常,我可以使用此代码并在没有 AVX2(仅 AVX)的旧计算机上运行它,并且它工作正常。然而,编译器似乎实际上正在发出 AVX2 指令(终于!),而不是我需要自己包含 SIMD 内在函数。

我想修改程序,以便支持两种途径(AVX2 和非 AVX2)。换句话说,我想要以下伪代码。

if (AVX2){
   callAVX2Version();
}else if (AVX){
   callAVXVersion();
}else{
   callSSEVersion();
}

void callAVX2Version(){
#pragma gcc -mavx2
}

void callAVXVersion(){
#pragma gcc -mavx
}
Run Code Online (Sandbox Code Playgroud)

我知道如何执行运行时检测部分,我的问题是是否可以执行功能特定的 SIMD 选择部分。

Mel*_*ter 6

简单干净的选择

gcc 目标属性可以像这样立即使用

[[gnu::target("avx")]]
void foo(){}

[[gnu::target("default")]]
void foo(){}

[[gnu::target("arch=sandybridge")]]
void foo(){}
Run Code Online (Sandbox Code Playgroud)

然后调用就变成了

foo();
Run Code Online (Sandbox Code Playgroud)

此选项无需以不同方式命名函数。例如,如果您查看godbolt,您会发现它为您创建了 @gnu_indirect_function。首先将其设置为 .resolver 函数。它读取 __cpu_model 以找出可以使用的内容,并将间接函数设置为该指针,以便任何后续调用都将是一个简单的间接函数。很简单不是吗。但您可能需要更接近原始代码库,因此还有其他方法

功能切换

如果您确实需要像原始示例中那样进行功能切换。可以使用以下内容。它使用了措辞优美的 buildtins,因此很明显您正在切换架构

[[gnu::target("avx")]]
int foo_avx(){ return 1;}

[[gnu::target("default")]]
int foo(){return 0;}

[[gnu::target("arch=sandybridge")]]
int foo_sandy(){return 2;}

int main ()
{
    if (__builtin_cpu_is("sandybridge"))
        return foo_sandy();
    else if (__builtin_cpu_supports("avx"))
        return  foo_avx();
    else
        return foo();
}
Run Code Online (Sandbox Code Playgroud)

定义您自己的间接函数

由于对其他人或平台更详细的原因,担心间接函数可能不受支持的用例。下面是一种与第一个选项相同的方法,但全部使用 C++ 代码。使用静态局部函数指针。这意味着您可以根据自己的喜好或在不支持内置的情况下对目标的优先级进行排序。您可以自己提供。

auto foo()
{
    using T = decltype(foo_default);
    static T* pointer = nullptr;
    //static int (*pointer)() = nullptr; 
    if (pointer == nullptr)
    {
    if (__builtin_cpu_is("sandybridge"))
        pointer = &foo_sandy;
    else if (__builtin_cpu_supports("avx"))
        pointer = &foo_avx;
    else
        pointer = &foo_default;        
    }
    return pointer();
};
Run Code Online (Sandbox Code Playgroud)

作为奖励

Godbolt上的以下模板化示例用于template<class ... Ts> 处理函数的重载,这意味着如果您定义了一系列函数,callXXXVersion(int)那么 foo(int) 将很乐意为您调用重载版本。只要你定义了整个家庭。