【发布时间】:2011-08-14 17:21:06
【问题描述】:
已更新 - 查看下方
将尽可能简短。如果需要,很高兴添加更多详细信息。
我有一些用于规范化向量的 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 代码要快一个数量级,但不会真正影响双版本的测量。 我进行了相当多的实验和搜索,但似乎无法找到合理的答案。
例如,我知道将结果转换为浮动时可能会受到惩罚,但这里没有发生任何事情。
谁能提供任何见解?在每次归一化之间调用 QueryPerformanceCounter 会大大降低 SIMD 代码的速度是什么原因?
感谢阅读:)
更多详情如下:
- 两种规范化方法都是内联的(在反汇编中验证)
- 正在发布中运行
- 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 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;
};
更新 所以感谢 Johns cmets,我想我已经设法确认是 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;
};
【问题讨论】: