簡體   English   中英

性能AVX / SSE匯編與內在函數

[英]Performance AVX/SSE assembly vs. intrinsics

我只是在嘗試檢查優化一些基本例程的最佳方法。 在這種情況下,我嘗試了一個非常簡單的將兩個float向量相乘的示例:

void Mul(float *src1, float *src2, float *dst)
{
    for (int i=0; i<cnt; i++) dst[i] = src1[i] * src2[i];
};

普通C的實現非常慢。 我使用AVX做了一些外部ASM,還嘗試使用內部函數。 這些是測試結果(時間越小越好):

ASM: 0.110
IPP: 0.125
Intrinsics: 0.18
Plain C++: 4.0

(使用MSVC 2013,SSE2編譯,嘗試使用Intel Compiler,結果幾乎相同)

如您所見,我的ASM代碼甚至擊敗了Intel Performance Primitives(可能是因為我做了很多分支工作,以確保可以使用AVX對齊的指令)。 但是我個人想利用內在方法,它更容易管理,並且我認為編譯器應該在優化所有分支和內容上做得最好(我的ASM代碼很糟糕,但是它更快)。 所以這是使用內在函數的代碼:

    int i;
    for (i=0; (MINTEGER)(dst + i) % 32 != 0 && i < cnt; i++) dst[i] = src1[i] * src2[i];

    if ((MINTEGER)(src1 + i) % 32 == 0)
    {
        if ((MINTEGER)(src2 + i) % 32 == 0)
        {
            for (; i<cnt-8; i+=8)
            {
                __m256 x = _mm256_load_ps( src1 + i); 
                __m256 y = _mm256_load_ps( src2 + i); 
                __m256 z = _mm256_mul_ps(x, y); 
                _mm256_store_ps(dst + i, z);
            };
        }
        else
        {
            for (; i<cnt-8; i+=8)
            {
                __m256 x = _mm256_load_ps( src1 + i); 
                __m256 y = _mm256_loadu_ps( src2 + i); 
                __m256 z = _mm256_mul_ps(x, y); 
                _mm256_store_ps(dst + i, z);
            };
        };
    }
    else
    {
        for (; i<cnt-8; i+=8)
        {
            __m256 x = _mm256_loadu_ps( src1 + i); 
            __m256 y = _mm256_loadu_ps( src2 + i); 
            __m256 z = _mm256_mul_ps(x, y); 
            _mm256_store_ps(dst + i, z);
        };
    };

    for (; i<cnt; i++) dst[i] = src1[i] * src2[i];

簡單:首先到達dst對齊到32個字節的地址,然后跳轉到檢查哪些源被對齊。

一個問題是,除非我在編譯器中啟用了AVX,否則開頭和結尾的C ++實現都不會使用AVX,這是我不希望的,因為這應該只是AVX的專業化,但是該軟件甚至可以在平台上運行, AVX不可用的地方。 遺憾的是,似乎沒有諸如vmovss之類的指令的內在函數,因此將AVX代碼與SSE混合使用可能會受到懲罰,編譯器會使用SSE。 但是,即使我在編譯器中啟用了AVX,它仍然不會低於0.14。

有什么想法如何優化它以使本征達到ASM代碼的速度?

您使用內在函數的實現與您在直接C語言中的實現不同:例如,如果使用參數Mul(p, p, p+1)調用函數Mul(p, p, p+1)怎么辦? 您會得到不同的結果。 純C版本的速度很慢,因為編譯器正在確保代碼完全符合您的要求。

如果希望編譯器基於三個數組不重疊的假設進行優化,則需要明確說明:

void Mul(float *src1, float *src2, float *__restrict__ dst)

甚至更好

void Mul(const float *src1, const float *src2, float *__restrict__ dst)

(我認為僅在輸出指針上使用__restrict__就足夠了,盡管也可以將其添加到輸入指針上也沒有什么壞處)

在具有AVX的CPU上,使用未對齊的負載幾乎不會帶來任何損失-我建議您將此小額損失與用於檢查對齊等的所有額外邏輯進行權衡,並且只使用一個循環+標量代碼來處理任何剩余元素:

   for (i = 0; i <= cnt - 8; i += 8)
   {
        __m256 x = _mm256_loadu_ps(src1 + i); 
        __m256 y = _mm256_loadu_ps(src2 + i); 
        __m256 z = _mm256_mul_ps(x, y); 
        _mm256_storeu_ps(dst + i, z);
   }
   for ( ; i < cnt; i++)
   {
       dst[i] = src1[i] * src2[i];
   }

更好的是,首先確保所有緩沖區都是32字節對齊的,然后才使用對齊的加載/存儲。

請注意,在這樣的循環中執行單個算術運算通常對於SIMD來說是一種不好的方法-執行時間將主要由加載和存儲決定-您應嘗試將此乘法與其他SIMD運算結合起來以減輕加載/存儲成本。

暫無
暫無

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

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