簡體   English   中英

SSE SIMD代碼中的性能問題

[英]Performance issue in SSE SIMD code

我有一個代碼可以將一個向量圍繞另一個向量旋轉到給定角度。 我使用四元數和此快速公式來執行此操作。 我編寫了兩個變體,分別使用和不使用SIMD編譯器內部函數。

變體1:

#include <xmmintrin.h>
#include <pmmintrin.h>
#include "test2.h"

static __v4sf cross_product_ (__v4sf a, __v4sf b)
{
    __v4sf r1 = a * _mm_shuffle_ps (b, b, _MM_SHUFFLE (1, 3, 2, 0));
    __v4sf r2 = b * _mm_shuffle_ps (a, a, _MM_SHUFFLE (1, 3, 2, 0));
    __v4sf r = r1 - r2;
    return _mm_shuffle_ps (r, r, _MM_SHUFFLE (1, 3, 2, 0));
}

static __v4sf rotate_vector_ (__v4sf base, __v4sf vect)
{
    __v4sf base_re = _mm_shuffle_ps (base, base, 0);
    __v4sf tmp = cross_product_ (base, vect);
    tmp = tmp * _mm_set_ps1 (2.0);

    __v4sf res = vect + base_re*tmp + cross_product_ (base, tmp);
    return res;
}

void rotate_vector (float base[], float vect[], float res[])
{
    __v4sf v = _mm_slli_si128 (_mm_load_ps (vect), 4);
    __v4sf r = rotate_vector_ (_mm_load_ps (base), v);
    r = _mm_srli_si128 (r, 4);
    _mm_store_ps (res, r);
}

變體2:

#include "test2.h"

static void cross_product (const float v1[], const float v2[], float res[])
{
    res[0] =  v1[1]*v2[2] - v1[2]*v2[1];
    res[1] = -v1[0]*v2[2] + v1[2]*v2[0];
    res[2] =  v1[0]*v2[1] - v1[1]*v2[0];
}

void rotate_vector (float base[], float vector[], float res[])
{
    float tmp[3], tmp2[3];
    int i;
    cross_product (base+1, vector, tmp);
    for (i=0; i<3; i++) tmp[i] *= 2.0;
    cross_product (base+1, tmp, tmp2);
    for (i=0; i<3; i++) res[i] = vector[i] + base[0]*tmp[i] + tmp2[i];
}

四元數的數據布局:

0......32......64......96......128 bits
 1(real)    i       j       k

對於矢量:

0......32......64......96......128 bits
    x       y       z      XXX

然后,我嘗試使用一個旋轉四元數(圍繞x軸旋轉90度)旋轉向量的預初始化數組。 占用大量內存!

#include <sys/time.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <strings.h>
#include "test2.h"

double gettime ()
{
    struct timeval tv;
    gettimeofday (&tv, NULL);
    return (double)tv.tv_sec + (0.000001 * (double)tv.tv_usec);
}

#define N 400000000

int main ()
{
    float z = sqrtf(2)/2;
    float a[4] __attribute__((aligned(16))) = {z,z,0,0};
    float (*b)[4] = aligned_alloc (16, 4*N*sizeof(float));
    int i;

    for (i=0; i<N; i++)
    {
        bzero (b[i], 16);
        b[i][i%3] = 4;
        b[i][0] = 1;
    }

    double time = gettime();
    for (i=0; i<N; i++)
    {
#if 0
        b[i][0] = 1;
#endif
        rotate_vector (a,b[i],b[i]);
    }
    time = gettime() - time;
    printf ("%f %f %f\n", b[0][0], b[0][1], b[0][2]);
    printf ("%f\n", time);
    return 0;
}

當使用clang 3.4 -O3 -msse3編譯並在AMD FX-6300處理器上執行時,SIMD變體比非SIMD快約10%。 但是,如果我刪除了#if/#endif ,換句話說,將某物寫入必須在每次迭代中旋轉的向量,則SIMD變體會變慢很多,並且執行速度比非SIMD慢2-2.5。 那么一次寫入如何減慢整個速度呢? 與緩存有關嗎? 我正在使用FreeBSD 10.2並嘗試使用pmcstat(8)測試此代碼,但沒有異常(例如高緩存未命中率或類似的東西)。

不過,似乎該性能在Atom處理器上並沒有受到影響(已在華碩Zenfone 2 ze551ml智能手機和Acer Aspire One Happy 2上網本上測試)。 那么也許這是處理器特定的問題? 還是我對SIMD的理解不正確,這不是應用它們的正確位置?

如果要在計算機上編譯此示例,則這里缺少test2.h(您將需要〜6Gb RAM):

#ifndef TEST2_H
#define TEST2_H

void quat_mul (float a[], float b[], float c[]);
void rotate_vector (float base[], float vect[], float res[]);

#endif

在執行矢量加載之前立即編寫單個元素將導致存儲轉發停頓。 這可能是損害SIMD版本性能的原因。 您可以使用可以記錄性能計數器的性能分析工具進行檢查。 請參閱Agner Fog的指南以及標簽Wiki中的其他鏈接。

哦,我剛剛注意到您說對Atom的性能不受影響。 這是支持我的理論的有力證據:Atom具有出色的存儲轉發功能,可以將數據從狹窄的存儲轉發到隨之而來的大量負載。 在所有其他x86微體系結構上,這會導致存儲轉發停頓並具有更高的延遲。 Agner Fog的microarch pdf解釋了這一點。


如果要修改單個矢量元素,最好使用_mm_insert_ps 如果您想修改很多,那就很可能了。 最好使用_mm_set_ps創建一個新矢量,然后使用_mm_blend_ps與舊矢量組合。 _mm_shuffle_ps_mm_unpacklo_ps / _mm_unpackhi_ps (或pd )也可以合並向量之間的數據。

您有足夠的數據(6.4 GB),緩存根本無關緊要。

在每次迭代中,您都要修改內存中的一個向量元素,並用load_ps加載向量,進行一些計算,然后將其寫回。 因此,有一個非向量寫入,后面是一個向量寫入。 第一次寫入將強制加載高速緩存行,然后將其部分弄臟,然后將其讀取為向量並寫入為向量。 這都很復雜,取決於處理器和內存系統的精確設計,可能會導致速度降低。

如果實際使用了b [i] [0],則很可能將賦值b [i] [0] = 1移到您調用的函數中。 因此,在向量版本中,您可以對向量b [i] load_ps進行加載,然后在向量寄存器中修改向量的第一個元素,從而避免干擾內存。

暫無
暫無

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

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