更新-在下面检查
会尽量简短。如果需要的话,我很乐意增加更多的细节。
我有一些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代码慢了这么多?
谢谢你的阅读:)
详情如下:
两种标准化方法都是内联的(在disassembly)
中进行验证)。
简单向量结构
_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;Helper struct
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的评论,我想我已经确认是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;
};发布于 2011-04-28 13:27:20
当只有运行循环的SSE代码时,处理器应该能够保持其管道满,并且每单位时间执行大量SIMD指令。当您在循环中添加计时器代码时,现在在每个容易优化的操作之间都会有一大堆非SIMD指令,可能更难预测。很可能,QueryPerformanceCounter调用的代价足以使数据操作变得无关紧要,或者它执行的代码的性质破坏了处理器以最大速度执行指令的能力(可能是由于缓存驱逐或未被很好预测的分支)。
您可以尝试在计时器类中注释掉对QPC的实际调用,看看它是如何执行的--这可能会帮助您发现问题是计时器对象的构造和破坏,还是QPC调用。同样,只需在循环中直接调用QPC,而不是制作定时器,看看比较如何。
发布于 2011-04-28 14:48:55
QPC是一个内核函数,调用它会导致上下文切换,这与任何等效的用户模式函数调用相比,其代价和破坏性都要大得多,并且肯定会消除处理器以正常速度进行处理的能力。此外,请记住,QPC/QPF是抽象的,需要它们自己的处理--这可能涉及SSE本身的使用。
https://stackoverflow.com/questions/5819227
复制相似问题