gpt4 book ai didi

c++ - 分析 SIMD 代码

转载 作者:可可西里 更新时间:2023-11-01 17:29:36 25 4
gpt4 key购买 nike

已更新 - 检查下方

将尽可能简短。如果需要,很乐意添加更多详细信息。

我有一些用于规范化 vector 的 sse 代码。我正在使用 QueryPerformanceCounter()(包装在辅助结构中)来衡量性能。

如果我这样测量

for( int j = 0; j < NUM_VECTORS; ++j )
{
Timer t(norm_sse);
NormaliseSSE( vectors_sse+j);
}

我得到的结果通常比用 4 个 double 代表一个 vector (在相同配置中测试)进行标准归一化要慢。

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 代码要快一个数量级,但并不真正影响 double 版本的测量。我已经做了一些实验和搜索,但似乎无法找到一个合理的答案来说明原因。

例如,我知道将结果转换为 float 时可能会受到惩罚,但这里没有任何惩罚。

任何人都可以提供任何见解吗?在每次规范化之间调用 QueryPerformanceCounter 会大大降低 SIMD 代码的速度,这是怎么回事?

感谢阅读:)

更多详情如下:

  • 两种规范化方法都是内联的(在反汇编中验证)
  • 正在发布中
  • 32位编译

简单 vector 结构

_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;
};

更新因此,感谢 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;
};

最佳答案

当只有 SSE 代码运行循环时,处理器应该能够保持其流水线满载并每单位时间执行大量 SIMD 指令。当您在循环中添加定时器代码时,现在在每个易于优化的操作之间有一大堆非 SIMD 指令,可能更难预测。 QueryPerformanceCounter 调用的开销可能足以使数据操作部分变得无关紧要,或者它执行的代码的性质严重破坏了处理器以最大速率继续执行指令的能力(可能是由于缓存逐出或分支没有很好的预测)。

您可以尝试在您的 Timer 类中注释掉对 QPC 的实际调用,看看它是如何执行的——这可能有助于您发现是 Timer 对象的构建和销毁问题,还是 QPC 调用问题。同样,尝试直接在循环中调用 QPC 而不是创建定时器,看看两者有何不同。

关于c++ - 分析 SIMD 代码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5819227/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com