首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Profiling代码

Profiling代码
EN

Stack Overflow用户
提问于 2011-04-28 13:16:31
回答 2查看 1.4K关注 0票数 8

更新-在下面检查

会尽量简短。如果需要的话,我很乐意增加更多的细节。

我有一些sse代码来规范向量。我使用QueryPerformanceCounter() (包装在一个帮助器结构中)来衡量性能。

如果我像这样测量

代码语言:javascript
复制
for( int j = 0; j < NUM_VECTORS; ++j )
{
  Timer t(norm_sse);
  NormaliseSSE( vectors_sse+j);
}

我得到的结果通常比标准的标准化要慢,4倍表示一个向量(在相同的配置中测试)。

代码语言:javascript
复制
for( int j = 0; j < NUM_VECTORS; ++j )
{
  Timer t(norm_dbl);
  NormaliseDBL( vectors_dbl+j);
}

然而,定时只是整个循环,如下所示

代码语言:javascript
复制
{
  Timer t(norm_sse);
  for( int j = 0; j < NUM_VECTORS; ++j ){
    NormaliseSSE( vectors_sse+j );
  }    
}

显示SSE代码的速度快了一个数量级,但并不真正影响双版本的度量。我做了相当多的实验和搜索,似乎找不到一个合理的答案。

例如,我知道当将结果抛到浮点时可能会受到惩罚,但是这里没有这样做。

有人能提供任何洞察力吗?在每个规范之间调用QueryPerformanceCounter是什么原因使SIMD代码慢了这么多?

谢谢你的阅读:)

详情如下:

两种标准化方法都是内联的(在disassembly)

  • Running in

  • 32位编译

中进行验证)。

简单向量结构

代码语言:javascript
复制
_declspec(align(16)) struct FVECTOR{
    typedef float REAL;
  union{
    struct { REAL x, y, z, w; };
    __m128 Vec;
  };
};

规范SSE的代码:

代码语言:javascript
复制
  __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 );   

代码规范双

代码语言:javascript
复制
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

代码语言:javascript
复制
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获取更多信息)。

代码语言:javascript
复制
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;
};
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-04-28 13:27:20

当只有运行循环的SSE代码时,处理器应该能够保持其管道满,并且每单位时间执行大量SIMD指令。当您在循环中添加计时器代码时,现在在每个容易优化的操作之间都会有一大堆非SIMD指令,可能更难预测。很可能,QueryPerformanceCounter调用的代价足以使数据操作变得无关紧要,或者它执行的代码的性质破坏了处理器以最大速度执行指令的能力(可能是由于缓存驱逐或未被很好预测的分支)。

您可以尝试在计时器类中注释掉对QPC的实际调用,看看它是如何执行的--这可能会帮助您发现问题是计时器对象的构造和破坏,还是QPC调用。同样,只需在循环中直接调用QPC,而不是制作定时器,看看比较如何。

票数 13
EN

Stack Overflow用户

发布于 2011-04-28 14:48:55

QPC是一个内核函数,调用它会导致上下文切换,这与任何等效的用户模式函数调用相比,其代价和破坏性都要大得多,并且肯定会消除处理器以正常速度进行处理的能力。此外,请记住,QPC/QPF是抽象的,需要它们自己的处理--这可能涉及SSE本身的使用。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5819227

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档