【问题标题】:How to combine constexpr and vectorized code?如何结合 constexpr 和矢量化代码?
【发布时间】:2021-08-15 23:45:49
【问题描述】:

我正在为 x64 和 neon 开发 C++ 内部包装器。我希望我的函数是 constexpr。我的动机类似于Constexpr and SSE intrinsics,但编译器 (GCC) 在 constexpr 函数中可能不支持#pragma omp simd 和内在函数。下面的代码只是一个演示(自动矢量化就足够了)。

struct FA{
    float c[4];
};

inline constexpr FA add(FA a, FA b){
    FA result{};
    #pragma omp simd            // clang error: statement not allowed in constexpr function
    for(int i = 0; i < 4; i++){ // GCC error: uninitialized variable 'i' in 'constexpr' function
        result.c[i] = b.c[i] + a.c[i];
    }
    return result;
}
struct FA2{
    __m128 c;
};


inline constexpr FA2 add2(FA2 a, FA2 b){
        FA2 result{};
        result.c = _mm_add_ps(a.c,b.c); // GCC error: call to non-'constexpr' function '__m128 _mm_add_ps(__m128, __m128)'
        return result;                  // fine with clang
}


无论如何,我必须提供参考 C++ 代码以实现可移植性。有没有一种代码高效的方式让编译器在编译时使用参考代码?

f(){
    if(){
        // constexpr version
    }else{
        // intrinsic version
    }
}

它应该适用于所有支持 omp、内在函数和 C++20 的编译器。

【问题讨论】:

  • 有趣的是,add() 可以在 MSVC 上编译,但是 add2 给出了这个错误:error C3615: constexpr function 'add2' cannot result in a constant expression
  • 英特尔接受这两种功能。
  • 这正是引入std::is_constant_evaluated 的场景类型
  • GCC/clang 头文件定义 _mm_add_ps 没有 constexpr 所以你基本上不走运,除非你使用编译器特定的东西,比如 a.c + b.c 来使用 GNU C 本机向量语法 (@987654328 GNU C 中的@ 是一个浮点向量,+ 操作符对它起作用。__m128i 是一个两个 long long 的向量)。哦,我对你已经链接的问答的回答已经有一个例子,在整数向量上使用 GNU C 本机向量语法 == 而不是 _mm_cmpeq_epi32 :P

标签: c++ openmp constexpr intrinsics


【解决方案1】:

使用std::is_constant_evaluated,你可以得到你想要的:

#include <type_traits>

struct FA{
    float c[4];
};

// Just for the sake of the example. Makes for nice-looking assembly.
extern FA add_parallel(FA a, FA b);

constexpr FA add(FA a, FA b) {
    if (std::is_constant_evaluated()) {
        // do it in a constexpr-friendly manner
        FA result{};
        for(int i = 0; i < 4; i++) {
            result.c[i] = b.c[i] + a.c[i];
        }
        return result;
    } else {
        // can be anything that's not constexpr-friendly.
        return add_parallel(a, b);
    }
}

constexpr FA at_compile_time = add(FA{1,2,3,4}, FA{5,6,7,8});

FA at_runtime(FA a) {
    return add(a, at_compile_time);
}

查看godbolt:https://gcc.godbolt.org/z/szhWKs3ec

【讨论】:

  • gcc.godbolt.org/z/n95Ta8P4n 在全局数组维度中使用 (unsigned)at_compile_time.c[0]; 以表明它确实是 100% 有效的 constexpr。如果您将其更改为 const ... at_compile_time 而不是 constexpr 它不会编译,即使您将 add 更改为始终使用简单的内联版本。 (所以简单的常量传播是不够的,或者 GCC 选择不这样做。)
猜你喜欢
  • 1970-01-01
  • 2018-03-15
  • 1970-01-01
  • 1970-01-01
  • 2013-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多