【问题标题】:why is 'fast inverse square root' slower than 1/sqrt() at extremely large float?为什么在非常大的浮点数下“快速平方根平方根”比 1/sqrt() 慢?
【发布时间】:2018-07-24 01:40:04
【问题描述】:

以下完整代码可以将fast inverse square root 的速度与 1/sqrt() 进行比较。根据维基百科中的sentence,(即该算法比用另一种方法计算平方根并通过浮点除法计算倒数快大约四倍。)

但这就是我在这里的原因:它比 1/sqrt() 慢。我的代码有问题吗?请。

    #include <stdio.h>
    #include <time.h>
    #include <math.h>

    float FastInvSqrt (float number);

    int
    main ()
    {
      float x = 1.0e+100;

      int N = 100000000;
      int i = 0;

      clock_t start2 = clock (); 
      do  
        {   
          float z = 1.0 / sqrt (x);
          i++;
        }   
      while (i < N); 
      clock_t end2 = clock (); 

      double time2 = (end2 - start2) / (double) CLOCKS_PER_SEC;

      printf ("1/sqrt() spends %13f sec.\n\n", time2);

      i = 0;
      clock_t start1 = clock (); 
      do  
        {   
          float y = FastInvSqrt (x);
          i++;
        }   
      while (i < N); 
      clock_t end1 = clock (); 

      double time1 = (end1 - start1) / (double) CLOCKS_PER_SEC;



      printf ("FastInvSqrt() spends %f sec.\n\n", time1);


      printf ("fast inverse square root is faster %f times than 1/sqrt().\n", time2/time1);

      return 0;
}

float
FastInvSqrt (float x)
{
  float xhalf = 0.5F * x;
  int i = *(int *) &x;  // store floating-point bits in integer
  i = 0x5f3759df - (i >> 1);    // initial guess for Newton's method
  x = *(float *) &i;            // convert new bits into float
  x = x * (1.5 - xhalf * x * x);        // One round of Newton's method
  //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
  //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
  //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
  return x;
}

结果如下:

1/sqrt() spends      0.850000 sec.

FastInvSqrt() spends 0.960000 sec.

fast inverse square root is faster 0.885417 times than 1/sqrt().

【问题讨论】:

  • 您在哪些值上测试您的功能?仅使用例如输入 1 运行代码可能会产生初始常量损失。所以也许可以尝试一些非常大的值,并在此基础上进行基准测试。
  • 您的代码是垃圾,因为循环内容不使用平方根算法的结果。重新编码,以便循环每次计算不同的倒数平方根并打印出结果的总和并启用优化。此外,您的逆平方根至少需要 3 次迭代才能收敛到单精度精度,并且应该同时计算 8 次,以便编译器可以使用 ymm 寄存器对操作进行矢量化。
  • @San Tseng 维基百科声称FastInvSqrt 比特定替代方案快四倍的参考文献是十五年前发表于 2003 年的一篇论文。没有什么特别的理由可以假设 2003 年硬件平台、工具链和库的真实情况今天仍然适用,因为从那时起所有这些都发生了重大变化(例如 x86 处理器上平方根的硬件指令已变为 从那时起快得多)。
  • @DillonDavis 禁用优化的计时测试是徒劳的。测试必须计算N不同的倒数平方根并且必须使用输出中的所有结果,否则优化器可以跳过循环。对于 O.P.,您的循环执行 100000001 次迭代,而不仅仅是 1 次。如果只计算 1 倒数平方根需要大约一秒钟,那么这将是可怜的,这不是正在发生的事情。
  • @user5713492 浮点单元作为一个整体变得更宽,但这适用于平方根和相乘。上次我检查,平方根和 add/mul/fma 之间的吞吐量比明显小于 15 年前,从 1:25 下降到 1:6(或大约)。

标签: c performance math square-root


【解决方案1】:

减少精确计算域的函数将具有更少的计算复杂度(意味着可以更快地计算它)。这可以被认为是针对其定义的子集优化函数形状的计算,或者类似于搜索算法,每个算法都最适合特定类型的输入(没有免费午餐定理)。

因此,将这个函数用于区间 [0, 1] 之外的输入(我认为它是针对它进行优化/设计的)意味着在其复杂性比其他可能专业化的更差(更高)的输入子集中使用它计算平方根的函数的变体。

您从库中使用的 sqrt() 函数本身(可能)也经过优化,因为它在某种 LUT 中具有预先计算的值(作为进一步近似的初始猜测);使用这样一个更“通用的函数”(意味着它覆盖了更多的领域,并试图通过例如预计算来提高效率;或者消除冗余计算,但这是有限的;或者在运行时最大化数据重用)有其复杂性限制,因为在间隔中使用的预计算选择越多,决策开销就越大;因此,在编译时知道您对 sqrt 的所有输入都在区间 [0, 1] 内将有助于减少运行时决策开销,因为您会提前知道要使用哪个专门的近似函数(或者您可以生成专门的每个感兴趣的时间间隔的函数,在编译时 -> 请参阅元编程)。

【讨论】:

    【解决方案2】:

    我更正我的代码如下: 1. 计算随机数,而不是固定数。 2.计算while循环内的时间消耗和总和。

    #include <stdio.h>
    #include <time.h>
    #include <math.h>
    #include <stdlib.h>
    
    float FastInvSqrt (float number);
    
    int
    main ()
    {
      float x=0;
      time_t t;
    
      srand((unsigned) time(&t));
    
      int N = 1000000;
      int i = 0;
    
      double sum_time2=0.0;
    
      do  
        {   
          x=(float)(rand() % 10000)*0.22158;
      clock_t start2 = clock (); 
          float z = 1.0 / sqrt (x);
      clock_t end2 = clock (); 
            sum_time2=sum_time2+(end2-start2);
          i++;
        }   
      while (i < N); 
    
    
      printf ("1/sqrt() spends %13f sec.\n\n", sum_time2/(double)CLOCKS_PER_SEC);
    
      double sum_time1=0.0;
    
      i = 0;
      do  
    
        {
          x=(float)(rand() % 10000)*0.22158;
      clock_t start1 = clock ();
          float y = FastInvSqrt (x);
      clock_t end1 = clock ();
            sum_time1=sum_time1+(end1-start1);
          i++;
        }
      while (i < N);
    
      printf ("FastInvSqrt() spends %f sec.\n\n", sum_time1/(double)CLOCKS_PER_SEC);
    
      printf ("fast inverse square root is faster %f times than 1/sqrt().\n", sum_time2/sum_time1);
    
      return 0;
    }
    
    float
    FastInvSqrt (float x)
    {
      float xhalf = 0.5F * x;
      int i = *(int *) &x;  // store floating-point bits in integer
      i = 0x5f3759df - (i >> 1);    // initial guess for Newton's method
      x = *(float *) &i;            // convert new bits into float
      x = x * (1.5 - xhalf * x * x);        // One round of Newton's method
      //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
      //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
      //x = x * (1.5 - xhalf * x * x);      // One round of Newton's method
      return x;
    }
    

    但快速平方根平方根仍然比 1/sqrt() 慢。

    1/sqrt() spends      0.530000 sec.
    
    FastInvSqrt() spends 0.540000 sec.
    
    fast inverse square root is faster 0.981481 times than 1/sqrt().
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-07-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多