[英]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的指南以及x86標簽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.