簡體   English   中英

對 FMA 操作進行更積極的優化

[英]More aggresive optimization for FMA operations

我想構建一個表示多個(比如N )算術類型的數據類型,並提供與使用運算符重載的算術類型相同的接口,這樣我就可以獲得像 Agner Fog 的vectorclass這樣的數據類型。

請看這個例子: Godbolt

#include <array>

using std::size_t;

template<class T, size_t S>
class LoopSIMD : std::array<T,S>
{
public:
    friend LoopSIMD operator*(const T a, const LoopSIMD& x){
        LoopSIMD result;
        for(size_t i=0;i<S;++i)
            result[i] = a*x[i];
        return result;
    }

    LoopSIMD& operator +=(const LoopSIMD& x){
        for(size_t i=0;i<S;++i){
            (*this)[i] += x[i];
        }
        return *this;
    }
};

constexpr size_t N = 7;
typedef LoopSIMD<double,N> SIMD;

SIMD foo(double a, SIMD x, SIMD y){
    x += a*y;
    return x;
}

這似乎在達到一定數量的元素時效果很好,gcc-10 為 6,clang-11 為 27。 對於大量元素,編譯器不再使用 FMA(例如vfmadd213pd )操作。 相反,它們分別進行乘法(例如vmulpd )和加法(例如vaddpd )。

問題:

  • 這種行為有充分的理由嗎?
  • 是否有任何編譯器標志可以增加上述 gcc 的 6 和 clang 的 27 的值?

謝謝!

對於 gcc 10.2,我執行了以下操作,並獲得了一些相當不錯的結果,使用相同的-Ofast -march=skylake -ffast-math作為您的 Godbolt 鏈接。

friend LoopSIMD operator*(const T a, const LoopSIMD& x) {
    LoopSIMD result;
    std::transform(x.cbegin(), x.cend(), result.begin(),
                   [a](auto const& i) { return a * i; });
    return result;
}

LoopSIMD& operator+=(const LoopSIMD& x) {
    std::transform(this->cbegin(), this->cend(), x.cbegin(), this->begin(),
                   [](auto const& a, auto const& b) { return a + b; });
    return *this;
}

std::transform有一些瘋狂的重載,所以我想我需要解釋一下。

第一個重載捕獲a ,將每個值相乘,並將其存儲回結果的開頭。

第二個重載作為一個zip將來自xthis兩個值相加,並將結果存儲回this

如果您沒有與operator+=operator*結婚,您可以像這樣創建自己的fma

    LoopSIMD& fma(const LoopSIMD& x, double a ){
        std::transform_inclusive_scan(
            x.cbegin(),
            x.cend(),
            this->begin(),
            std::plus{},
            [a](auto const& i){return i * a;},
            0.0);
        return *this;
    }

這需要 c++17,但將循環保持 SIMD 指令

foo(double, LoopSIMD<double, 40ul>&, LoopSIMD<double, 40ul> const&):
        xor     eax, eax
        vxorpd  xmm1, xmm1, xmm1
.L2:
        vfmadd231sd     xmm1, xmm0, QWORD PTR [rsi+rax]
        vmovsd  QWORD PTR [rdi+rax], xmm1
        add     rax, 8
        cmp     rax, 320
        jne     .L2
        ret

您也可以簡單地制作自己的 fma 函數:

template<class T, size_t S>
class LoopSIMD : std::array<T,S>
{
public:
    friend LoopSIMD fma(const LoopSIMD& x, const T y, const LoopSIMD& z) {
        LoopSIMD result;
        for (size_t i = 0; i < S; ++i) {
            result[i] = std::fma(x[i], y, z[i]);
        }
        return result;
    }
    friend LoopSIMD fma(const T y, const LoopSIMD& x, const LoopSIMD& z) {
        LoopSIMD result;
        for (size_t i = 0; i < S; ++i) {
            result[i] = std::fma(y, x[i], z[i]);
        }
        return result;
    }
    // And more variants, taking `const LoopSIMD&, const LoopSIMD&, const T`, `const LoopSIMD&, const T, const T`, etc
};

SIMD foo(double a, SIMD x, SIMD y){
    return fma(a, y, x);
}

但是為了首先進行更好的優化,您應該對齊您的數組。 如果你這樣做,你的原始代碼優化得很好:

constexpr size_t next_power_of_2_not_less_than(size_t n) {
    size_t pow = 1;
    while (pow < n) pow *= 2;
    return pow;
}

template<class T, size_t S>
class LoopSIMD : std::array<T,S>
{
public:
    // operators
} __attribute__((aligned(next_power_of_2_not_less_than(sizeof(T[S])))));

// Or with a c++11 attribute
/*
template<class T, size_t S>
class [[gnu::aligned(next_power_of_2_not_less_than(sizeof(T[S])))]] LoopSIMD : std::array<T,S>
{
public:
    // operators
};
*/

SIMD foo(double a, SIMD x, SIMD y){
    x += a * y;
    return x;
}

我發現給出的示例有所改進。

在循環 GCC 設法使 FMA 優化達到N=71之前添加#pragma omp simd

https://godbolt.org/z/Y3T1rs37W

如果使用 AVX512,尺寸可能會進一步改善:

https://godbolt.org/z/jWWPP7W5G

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM