最佳答案
我已经在 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.9 ns/float。我还尝试了 卡马克古怪的牛顿-拉斐逊近似技术,它的运行速度甚至比硬件还要好,为4.3 ns/float,尽管在210中有1个错误(这对我的目的来说太多了)。
当我尝试对 互惠互利的平方根执行 SSE 操作,然后使用乘法得到平方根(x * 1/& radic; x = & radic; x)时,情况非常糟糕。尽管这需要两个相互依赖的操作,但它是迄今为止最快的解决方案,为1.24 ns/float,精确到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 的内置硬件平方根操作码 < em > 比用另外两个数学运算合成它要慢?
我相信这是行动本身的成本,因为我已经证实:
(编辑: stephentyrone 正确地指出,对长串数字的操作应该使用向量化 SIMD 打包操作,比如 rsqrtps
& mdash,但这里的数组只是为了测试目的,我真正想测量的是在无法向量化的代码中使用的 标量性能。)