为什么SSE标量的sqrt(x)的比rsqrt(X)* X慢?(Why is SSE scalar

2019-07-21 05:01发布

我一直在剖析我们的一些核心数学上的英特尔酷睿双核,在注视我注意到一些奇怪的各种方法来平方根:使用SSE标量运算,它是更快采取倒数平方根和乘以拿到开方,比它是使用原生的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 /浮充使用的x87 FPU,这是可怜坏:

inline float TestSqrtFunction( float in )
{  return sqrt(in); }

接下来的事情我试图用一种内在强制编译器使用上证所标开方操作码:

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 /浮动。 我也试过卡马克的古怪牛顿迭代逼近技术 ,其运行时间比硬件,为4.3ns /浮甚至更好,虽然以1比2 10的错误(这是太多了,我的目的)。

所述讲给是当我尝试SSE运算为倒数平方根,然后使用乘法获得的平方根(X * 1 /√x=√x)。 尽管这需要两个相关的操作,这是迄今为止最快的解决方案,为1.24ns / 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
}

我的问题是基本上怎么办? 为什么上证所内置到硬件平方根操作码于合成出来另外两个数学运算的?

我敢肯定,这是真正的运算本身的成本,因为我已经验证:

  • 在缓存中的所有数据拟合,并访问是顺序
  • 将内联函数
  • 展开循环没什么区别
  • 编译器标志被设置为全面优化(与装配好,我选中)

编辑 :stephentyrone正确地指出,在数字应该使用的长字符串操作向量化SIMD包装OPS,像rsqrtps -但这里的阵列仅用于测试目的:什么我真的试图测量是在使用性能代码不能被量化。)

Answer 1:

sqrtss给出了一个正确舍入的结果。 rsqrtss给出了一个近似倒数,精确到大约11位。

sqrtss正在产生一个更为精确的结果,当需要精度。 rsqrtss存在时的近似足够的情况下,但需要的速度。 如果你看过Intel的文档,你还会发现,让几乎全精度(〜精度为23位,如果我记得正确)的指令序列(倒数平方根近似后跟一个牛顿迭代步骤),还是有点速度比sqrtss

编辑:如果速度是至关重要的,你实际上调用这个在许多价值观的循环,你应该使用这些指令的矢量版本rsqrtpssqrtps ,这两者的处理每个指令四个浮点。



Answer 2:

这也是真正的分裂。 MULSS(一个,RCPSS(b))的比DIVSS方式更快(A,B)。 事实上,它仍然是更快,甚至当你增加它的精确度与牛顿 - 拉夫逊迭代。

英特尔和AMD在他们的优化手册都建议使用此技术。 在不需要IEEE-754遵从应用程序,使用DIV /开方的唯一原因是代码的可读性。



Answer 3:

而不是提供一个答案,实际上可能是不正确的(我也不会去检查或争论缓存和其他的东西,比方说它们是相同的),我会尽量给你指向能回答你的问题的根源。
差异可能在于如何开方和rsqrt计算。 你可以在这里阅读更多http://www.intel.com/products/processor/manuals/ 。 我建议从阅读您使用的处理器功能开始,有一些信息,特别是有关rsqrt(CPU使用内部查找表有巨大的逼近,这使得它更易于得到的结果)。 它可能看起来,这rsqrt是如此比开方快得多,即1次额外MUL操作(这是不昂贵的)可能不会改变这里的情况。

编辑:一些事实,可能是值得一提:
1.当我在做一些微optimalizations我的图形库,我已经用于向量的计算长度rsqrt。 (而不是开方,我乘我的平方和由它rsqrt,这是你在你的测试做什么),它表现较好。
2.计算使用简单的查找表可能会更容易,作为rsqrt,当x趋于无穷大,1 / SQRT(X)变为0,因此对于小x的函数值不会改变(很多),而对于rsqrt开方 - 它趋于无穷大,所以它的简单情况下)。

此外,澄清:我不知道,我已经找到了在书本上,我联系,但我敢肯定,我读过rsqrt使用一些查找表,并应仅用于,当结果并不需要准确的说,虽然 - 我可能是错的为好,因为这是前一段时间:)。



Answer 4:

牛顿-拉夫逊收敛到零f(x)使用增量等于-f/f'其中f'是衍生物。

对于x=sqrt(y)则可以尝试解决f(x) = 0x使用f(x) = x^2 - y ;

然后增量为: dx = -f/f' = 1/2 (x - y/x) = 1/2 (x^2 - y) / x其中中有一个缓慢的鸿沟。

可以尝试其它功能(如f(x) = 1/y - 1/x^2 ),但它们将同样复杂。

让我们来看看1/sqrt(y)现在。 您可以尝试f(x) = x^2 - 1/y ,但是这将是同样复杂: dx = 2xy / (y*x^2 - 1)的实例。 对于一个非显而易见的替代选择f(x)为: f(x) = y - 1/x^2

然后: dx = -f/f' = (y - 1/x^2) / (2/x^3) = 1/2 * x * (1 - y * x^2)

啊! 这不是一个简单的表达,但你只有在乘它,没有鸿沟。 =>更快!

和:完全更新一步new_x = x + dx则写着:

x *= 3/2 - y/2 * x * x是一件容易的事。



Answer 5:

这是更快becausse这些指令忽略舍入模式,并且不处理莲花点异常或dernormalized号码。 由于这些原因,是管道容易得多,推测和执行其他指令FP坏了。



文章来源: Why is SSE scalar sqrt(x) slower than rsqrt(x) * x?