更新 - 请查看下方
尽量简短地说明。如有需要,我很乐意添加更多细节。
我有一些用于向量归一化的SSE代码。 我正在使用QueryPerformanceCounter(包装在帮助结构中)来测量性能。
如果我像这样测量:
for( int j = 0; j < NUM_VECTORS; ++j )
{
Timer t(norm_sse);
NormaliseSSE( vectors_sse+j);
}
我得到的结果通常比使用4个双精度表示向量进行标准归一化要慢(在相同的配置下进行测试)。
for( int j = 0; j < NUM_VECTORS; ++j )
{
Timer t(norm_dbl);
NormaliseDBL( vectors_dbl+j);
}
然而,仅仅像这样计时整个循环是不够准确的。
{
Timer t(norm_sse);
for( int j = 0; j < NUM_VECTORS; ++j ){
NormaliseSSE( vectors_sse+j );
}
}
展示了SSE代码比普通版本快一个数量级,但实际上并没有对双精度版本的测量产生影响。我进行了一些实验和搜索,但似乎找不到一个合理的答案。
例如,我知道将结果转换为float时可能会有惩罚,但这里没有发生任何情况。
有人可以提供任何见解吗?是什么导致在每个normalise之间调用QueryPerformanceCounter使SIMD代码变得如此缓慢?
谢谢阅读 :)
以下是更多细节:
- 两种normalise方法都是内联的(在反汇编中验证) - 在发布模式下运行 - 32位编译
简单向量结构
_declspec(align(16)) struct FVECTOR{
typedef float REAL;
union{
struct { REAL x, y, z, w; };
__m128 Vec;
};
};
规范化SSE代码:
__m128 Vec = _v->Vec;
__m128 sqr = _mm_mul_ps( Vec, Vec ); // Vec * Vec
__m128 yxwz = _mm_shuffle_ps( sqr, sqr , 0x4e );
__m128 addOne = _mm_add_ps( sqr, yxwz );
__m128 swapPairs = _mm_shuffle_ps( addOne, addOne , 0x11 );
__m128 addTwo = _mm_add_ps( addOne, swapPairs );
__m128 invSqrOne = _mm_rsqrt_ps( addTwo );
_v->Vec = _mm_mul_ps( invSqrOne, Vec );
规范化double类型的代码
double len_recip = 1./sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
v->x *= len_recip;
v->y *= len_recip;
v->z *= len_recip;
辅助结构体
struct Timer{
Timer( LARGE_INTEGER & a_Storage ): Storage( a_Storage ){
QueryPerformanceCounter( &PStart );
}
~Timer(){
LARGE_INTEGER PEnd;
QueryPerformanceCounter( &PEnd );
Storage.QuadPart += ( PEnd.QuadPart - PStart.QuadPart );
}
LARGE_INTEGER& Storage;
LARGE_INTEGER PStart;
};
更新 感谢John的评论,我想我已经确认了是QueryPerformanceCounter对我的simd代码产生了负面影响。
我添加了一个新的计时器结构,直接使用RDTSC进行测量,并且看起来给出了我期望的一致结果。结果仍然比整个循环的计时慢得多,而不是每次迭代分别计时,但我认为这是因为获取RDTSC需要刷新指令流水线(有关更多信息,请查看http://www.strchr.com/performance_measurements_with_rdtsc)。
struct PreciseTimer{
PreciseTimer( LARGE_INTEGER& a_Storage ) : Storage(a_Storage){
StartVal.QuadPart = GetRDTSC();
}
~PreciseTimer(){
Storage.QuadPart += ( GetRDTSC() - StartVal.QuadPart );
}
unsigned __int64 inline GetRDTSC() {
unsigned int lo, hi;
__asm {
; Flush the pipeline
xor eax, eax
CPUID
; Get RDTSC counter in edx:eax
RDTSC
mov DWORD PTR [hi], edx
mov DWORD PTR [lo], eax
}
return (unsigned __int64)(hi << 32 | lo);
}
LARGE_INTEGER StartVal;
LARGE_INTEGER& Storage;
};