[英]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.