我一直在Intel Core Duo上进行一些核心数学分析,在查看各种平方根方法时,我注意到了一些奇怪的事情:使用SSE标量运算,倒数平方根乘以它会更快获取sqrt,而不是使用本机sqrt操作码!
我正在用类似这样的循环进行测试:
inline float TestSqrtFunction( float in );
void TestFunc()
{
#define ARRAYSIZE 4096
#define NUMITERS 16386
float flIn[ ARRAYSIZE ]; // filled with random numbers ( 0 .. 2^22 )
float flOut [ ARRAYSIZE ]; // filled with 0 to force fetch into L1 cache
cyclecounter.Start();
for ( int i = 0 ; i < NUMITERS ; ++i )
for ( int j = 0 ; j < ARRAYSIZE ; ++j )
{
flOut[j] = TestSqrtFunction( flIn[j] );
// unrolling this loop makes no difference -- I tested it.
}
cyclecounter.Stop();
printf( "%d loops over %d floats took %.3f milliseconds",
NUMITERS, ARRAYSIZE, cyclecounter.Milliseconds() );
}
我已经为TestSqrtFunction使用了几种不同的主体进行了尝试,并且确实有一些时机让我很头疼。到目前为止,最糟糕的是使用本机sqrt()函数并让“智能”编译器“优化”。在24ns / float的情况下,使用x87 FPU确实很糟糕:
inline float TestSqrtFunction( float in )
{ return sqrt(in); }
我尝试的下一件事是使用内部函数强制编译器使用SSE的标量sqrt操作码:
inline void SSESqrt( float * restrict pOut, float * restrict pIn )
{
_mm_store_ss( pOut, _mm_sqrt_ss( _mm_load_ss( pIn ) ) );
// compiles to movss, sqrtss, movss
}
效果更好,为11.9ns / float。我还尝试了Carmack's wacky Newton-Raphson approximation technique,它的运行速度比硬件还要好,为4.3ns / float,尽管210中有1的错误(这对我来说太大了)。
当我尝试SSE op求倒数平方根,然后使用乘积获得平方根时,就变得笨拙了(x * 1 /√x=√x)。即使需要两个相关的操作,它还是迄今为止最快的解决方案,速度为1.24ns /浮点,精确度为2-14:
inline void SSESqrt_Recip_Times_X( float * restrict pOut, float * restrict pIn )
{
__m128 in = _mm_load_ss( pIn );
_mm_store_ss( pOut, _mm_mul_ss( in, _mm_rsqrt_ss( in ) ) );
// compiles to movss, movaps, rsqrtss, mulss, movss
}
我的问题基本上是什么给的? 为什么SSE的内置于硬件的平方根操作码比从其他两个数学运算中合成出来的速度慢?
我确信这确实是操作本身的成本,因为我已经验证:
访问是顺序的
(编辑:stephentyrone正确指出,对长数字串进行的操作应使用矢量化SIMD打包操作,例如
rsqrtps
-但此处的数组数据结构仅用于测试目的:我真正要衡量的是标量性能在无法向量化的代码中使用。) 最佳答案
sqrtss
给出正确的舍入结果。 rsqrtss
给出倒数的近似值,精确到大约11位。
当需要准确性时,sqrtss
会生成更准确的结果。对于近似值足够但需要速度的情况,存在rsqrtss
。如果您阅读了英特尔的文档,您还会发现一条指令序列(平方根的倒数,后跟一个牛顿-拉夫森步长),几乎可以提供全精度(大约23位精度,如果我没记错的话),并且仍然有些比sqrtss
快。
编辑:如果速度至关重要,并且您实际上是在循环调用多个值时,则应使用这些指令的矢量化版本rsqrtps
或sqrtps
,这两个指令每条指令都会处理四个浮点数。