performance - 为什么 SSE 标量 sqrt(x) 比 rsqrt(x) * x 慢?

标签 performance assembly floating-point x86 sse

我一直在英特尔酷睿双核上分析我们的一些核心数学,在研究各种求平方根的方法时,我注意到一些奇怪的事情:使用 SSE 标量运算,取倒数平方根的速度更快并将其相乘以获得 sqrt,而不是使用 native 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 的几个不同的主体进行了尝试,并且我得到了一些确实令人摸不着头脑的计时。到目前为止,最糟糕的是使用 native 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 运算计算倒数平方根,然后使用乘法来获得平方根 ( 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
}

我的问题基本上是给出了什么为什么 SSE 的内置硬件平方根操作码比其他两个数学运算合成的要慢

我确信这确实是操作本身的成本,因为我已经验证过:

  • 所有数据都适合缓存,并且 访问是顺序的
  • 函数是内联的
  • 展开循环没有什么区别
  • 编译器标志设置为完全优化(并且程序集很好,我检查过)

(编辑:stephentyrone正确地指出,对长数字字符串的操作应该使用向量化SIMD打包操作,例如rsqrtps - 但这里的数组数据结构是为了仅用于测试目的:我真正想要测量的是在无法矢量化的代码中使用的标量性能。)

最佳答案

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

当需要准确性时,

sqrtss 正在生成更加准确的结果。 rsqrtss 存在于近似值足够但需要速度的情况。如果您阅读英特尔的文档,您还会发现一个指令序列(倒数平方根近似,后跟一个牛顿-拉夫森步骤),它提供了几乎完整的精度(如果我没记错的话,精度约为 23 位),并且仍然有点比 sqrtss 更快。​​

编辑:如果速度至关重要,并且您确实在循环中调用许多值,则应该使用这些指令的矢量化版本,rsqrtpssqrtps,两者每条指令都处理四个 float 。

关于performance - 为什么 SSE 标量 sqrt(x) 比 rsqrt(x) * x 慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1528727/

相关文章:

node.js - Websocket (node.js) 连接限制,客户端在达到 400-450 个连接后断开连接

performance - 程序如何在不读取整个文件的情况下确定文件的大小?

php - 处理从 MySQL 到 PHP 再到 JSON 客户端的大数据集

windows - 为什么 Windows x64 调用约定不使用 XMM 寄存器来传递超过 4 个整数参数?

c++ - 使用 NASM 汇编器时的问题

python - C++ Exprtk 与 Python eval()

assembly - 为什么 MIPS 指令 "add"有时会给我一个算术错误,但并非总是如此?

Python - 除法结果中的有效位数

c++ - std::reduce 对于 float 有多安全?

Delphi错误的 double 计算