簡體   English   中英

C內在函數,SSE2點積和gcc -O3生成的匯編

[英]C intrinsics, SSE2 dot product and gcc -O3 generated assembly

我需要使用SSE2編寫一個點積(沒有_mm_dp_ps也沒有_mm_hadd_ps):

#include <xmmintrin.h>

inline __m128 sse_dot4(__m128 a, __m128 b)
{
    const __m128 mult = _mm_mul_ps(a, b);
    const __m128 shuf1 = _mm_shuffle_ps(mult, mult, _MM_SHUFFLE(0, 3, 2, 1));
    const __m128 shuf2 = _mm_shuffle_ps(mult,mult, _MM_SHUFFLE(1, 0, 3, 2));
    const __m128 shuf3 = _mm_shuffle_ps(mult,mult, _MM_SHUFFLE(2, 1, 0, 3));

    return _mm_add_ss(_mm_add_ss(_mm_add_ss(mult, shuf1), shuf2), shuf3);
}

但我看了生成的匯編程序與gcc 4.9(實驗)-O3,我得到:

    mulps   %xmm1, %xmm0
    movaps  %xmm0, %xmm3         //These lines
    movaps  %xmm0, %xmm2         //have no use
    movaps  %xmm0, %xmm1         //isn't it ?
    shufps  $57, %xmm0, %xmm3
    shufps  $78, %xmm0, %xmm2
    shufps  $147, %xmm0, %xmm1
    addss   %xmm3, %xmm0
    addss   %xmm2, %xmm0
    addss   %xmm1, %xmm0
    ret

我想知道為什么gcc在xmm1,2和3中復制xmm0 ...這是我使用標志得到的代碼:-march = native(看起來更好)

    vmulps  %xmm1, %xmm0, %xmm1
    vshufps $78, %xmm1, %xmm1, %xmm2
    vshufps $57, %xmm1, %xmm1, %xmm3
    vshufps $147, %xmm1, %xmm1, %xmm0
    vaddss  %xmm3, %xmm1, %xmm1
    vaddss  %xmm2, %xmm1, %xmm1
    vaddss  %xmm0, %xmm1, %xmm0
    ret

這是一個只使用原始SSE指令的點積,它也會在每個元素上調整結果:

inline __m128 sse_dot4(__m128 v0, __m128 v1)
{
    v0 = _mm_mul_ps(v0, v1);

    v1 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(2, 3, 0, 1));
    v0 = _mm_add_ps(v0, v1);
    v1 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(0, 1, 2, 3));
    v0 = _mm_add_ps(v0, v1);

    return v0;
}

它是5個SIMD指令(而不是7個),但沒有隱藏延遲的真正機會。 任何元素都將保存結果,例如, float f = _mm_cvtss_f32(sse_dot4(a, b);

haddps指令的延遲非常糟糕。 使用SSE3:

inline __m128 sse_dot4(__m128 v0, __m128 v1)
{
    v0 = _mm_mul_ps(v0, v1);

    v0 = _mm_hadd_ps(v0, v0);
    v0 = _mm_hadd_ps(v0, v0);

    return v0;
}

雖然它只有3條SIMD指令,但速度可能較慢。 如果您一次可以執行多個點積,則可以在第一種情況下交錯指令。 在最近的微架構上,Shuffle非常快。

您粘貼的第一個列表僅適用於SSE體系結構。 大多數SSE指令僅支持兩種操作數語法:指令的形式為a = a OP b

在你的代碼中, amult 因此,如果沒有復制並直接傳遞mult (在您的示例中為xmm0 ),則其值將被覆蓋,然后因剩余的_mm_shuffle_ps指令而丟失

通過在第二個列表中傳遞march=native ,您啟用了AVX指令。 AVX使SSE intructions能夠使用三個操作數語法: c = a OP b 在這種情況下,不必覆蓋任何源操作數,因此您不需要其他副本。

讓我建議,如果您要使用SIMD來制作點積,那么您可以嘗試找到一種方法同時對多個向量進行操作。 例如,對於SSE,如果你有四個向量,並且你想要使用固定向量的點積,那么你排列數據,如(xxxx),(yyyy),(zzzz),(wwww),並添加每個SSE向量,並得到四個點產品的結果一次。 這將使您獲得100%(四倍的加速)效率,並且不僅限於4分量矢量,它對於n分量矢量也是100%有效的。 這是一個僅使用SSE的示例。

#include <xmmintrin.h>
#include <stdio.h>

void dot4x4(float *aosoa, float *b, float *out) {   
    __m128 vx = _mm_load_ps(&aosoa[0]);
    __m128 vy = _mm_load_ps(&aosoa[4]);
    __m128 vz = _mm_load_ps(&aosoa[8]);
    __m128 vw = _mm_load_ps(&aosoa[12]);
    __m128 brod1 = _mm_set1_ps(b[0]);
    __m128 brod2 = _mm_set1_ps(b[1]);
    __m128 brod3 = _mm_set1_ps(b[2]);
    __m128 brod4 = _mm_set1_ps(b[3]);
    __m128 dot4 = _mm_add_ps(
        _mm_add_ps(_mm_mul_ps(brod1, vx), _mm_mul_ps(brod2, vy)),
        _mm_add_ps(_mm_mul_ps(brod3, vz), _mm_mul_ps(brod4, vw)));
    _mm_store_ps(out, dot4);

}

int main() {
    float *aosoa = (float*)_mm_malloc(sizeof(float)*16, 16);
    /* initialize array to AoSoA vectors v1 =(0,1,2,3}, v2 = (4,5,6,7), v3 =(8,9,10,11), v4 =(12,13,14,15) */
    float a[] = {
        0,4,8,12,
        1,5,9,13,
        2,6,10,14,
        3,7,11,15,
    };
    for (int i=0; i<16; i++) aosoa[i] = a[i];

    float *out = (float*)_mm_malloc(sizeof(float)*4, 16);
    float b[] = {1,1,1,1};
    dot4x4(aosoa, b, out);
    printf("%f %f %f %f\n", out[0], out[1], out[2], out[3]);

    _mm_free(aosoa);
    _mm_free(out);
}

(事實上​​,盡管所有上升的選票,這個問題發布時給出的答案都沒有達到我的期望。這是我等待的答案。)

SSE指令

shufps $IMM, xmmA, xmmB

不起作用

xmmB = f($IMM, xmmA) 
//set xmmB with xmmA's words shuffled according to $IMM

但作為

xmmB = f($IMM, xmmA, xmmB) 
//set xmmB with 2 words of xmmA and 2 words of xmmB according to $IMM

這就是為什么需要從xmm0xmm1..3mulps副本的副本。

暫無
暫無

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

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