sha*_*zum 1 c performance x86 sse
我有一个代码可以将一个向量围绕另一个向量旋转到给定角度。我使用四元数和这个快速公式来做到这一点。我编写了两个变体,使用和不使用 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);
}
Run Code Online (Sandbox Code Playgroud)
变体 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];
}
Run Code Online (Sandbox Code Playgroud)
四元数的数据布局:
0......32......64......96......128 bits
1(real) i j k
Run Code Online (Sandbox Code Playgroud)
对于向量:
0......32......64......96......128 bits
x y z XXX
Run Code Online (Sandbox Code Playgroud)
然后,我尝试使用一个旋转四元数来旋转预初始化的向量数组(围绕 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;
}
Run Code Online (Sandbox Code Playgroud)
-O3 -msse3
使用 clang 3.4 编译并在 AMD FX-6300 处理器上执行时,SIMD 变体比非 SIMD 快约 10% 。但是,如果我删除#if/#endif
,换句话说,向必须在每次迭代时旋转的向量写入一些内容,SIMD 变体会减慢很多,并且执行速度比非 SIMD 慢约 2-2.5。那么单次写入如何减慢整个过程呢?和缓存有关系吗?我正在使用 FreeBSD 10.2 并尝试使用 pmcstat(8) 测试此代码,但没有发现任何异常情况(例如高缓存未命中率或类似的情况)。
不过,Atom 处理器的性能似乎并未受到影响(在 Asus 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
Run Code Online (Sandbox Code Playgroud)
在执行矢量加载之前写入单个元素将导致存储转发停顿。这可能是影响 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
)也可以组合向量之间的数据。
归档时间: |
|
查看次数: |
357 次 |
最近记录: |