【问题标题】:Improve performance custom square root algorithm C#提高性能自定义平方根算法 C#
【发布时间】:2018-09-03 22:00:25
【问题描述】:

这是我提出的一种计算平方根的算法,目前在使用循环 n 次和秒表测试它时,它比 C# Math.Sqrt() 慢大约 20-100 倍;

有没有什么方法可以提高这个函数的性能,或者性能是否和这个特定算法一样好?

我的 C# 平方根算法:

static class MyMath
{
    public static double Sqrt(double _d)
    {
        double x = 0;
        double y = 2;
        double z = 1;
        double w = _d;
        double h = 1;
        double t = 0;
        double px = 0;
        int itr = 0;
        while (true)
        {
            w = (w / y);
            h *= y;
            if (h > w)
            {
                t = w;
                w = h;
                h = t;
                z *= 0.5;
                y = (1 + z);
            }
            x = ((w + h) * 0.5);
            if (itr >= 100 || w == h || px == x)
            {
                return (x);
            }
            px = x;
            itr++;
        }
    }
}

我如何测试性能:

using System.Diagnostics;


Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 10000; i++)
{
    MyMath.Sqrt(2);
}
sw.Stop();
Debug.Print(sw.ElapsedTicks.ToString());

EDIT3:略微改进的版本:

    static class MyMath
    {
        public static double Sqrt(double _d)
        {
            double x = 0;
            double y = 2;
            double z = 1;
            double w = _d;
            double h = 1;
            double t = 0;
            double px = 1;
            while (true)
            {
                if (x == px)
                {
                    return ((w + h) * 0.5);
                }
                if (w < h)
                {
                    t = w;
                    w = h;
                    h = t;
                    z *= 0.25;
                    y = (z + 1);
                    px = x;
                }
                w /= y;
                h *= y;
                x = (w + h);
            }
        }
    }

EDIT3:更新了略微改进的版本2 + 更改了基准方法2: (在发布模式下运行

        Stopwatch sw = new Stopwatch();
        int n = 100000;
        double[] squareArr = new double[n];
        Random rng = new Random(1234);
        for (int i = 0; i < n; i++)
        {
            squareArr[i] = rng.Next(1, 100000);
        }
        sw.Start();
        for (int i = 0; i < n; i++)
        {
            squareArr[i] = MyMath.Sqrt(squareArr[i]) ;
        }
        sw.Stop();
        debugBox.AppendText("AverageTime: " + (sw.ElapsedTicks / (double)n).ToString());

目前根据我的测试默认 Math.Sqrt() ~0.086 Ticks 和 mySqrt() ~4.8 Ticks。

编辑 4:(修复错误:在 if 语句中移动了 px = x)

【问题讨论】:

  • 那么为什么不使用Math.Sqrt呢?你为什么要重新发明轮子?如果有更快的方法,Math.Sqrt 会这样做...
  • 这个问题更适合codereview.stackexchange.com
  • Math.Sqrt 在内部用原生 C++ 实现,比 C# 性能要好得多,而且比你想象的要优化得多。
  • mySqrt(12544).ToString("G17") == 111.99999999999999。不要被默认的四舍五入的字符串表示所迷惑。
  • 您的方法比 Math.Sqrt 慢得多,这是意料之中的。在现代 PC 上,sqrt 花费的时间与单个部门大致相同,例如在 Intel Haswell 上,FDIV 指令需要 10-24 个周期,FSQRT 指令需要 10-23 个周期。还有另一个浮点除法指令 divpd,但它只是稍微快一点,10-20 个周期。 agner.org/optimize/instruction_tables.pdf 你的循环中有很多部门。

标签: c# algorithm performance


【解决方案1】:

我创建了一个更新版本的算法,这个算法执行 ~0.97 Ticks vs Default Math.Sqrt ~0.086 所以仍然慢了大约 11 倍,它不会使用.ToString("G17") 检查时始终具有完整的双精度,但它非常接近,但是现在使用 sqrt(12544) 是正确的,并且与算法的原始版本相比,它是一个相当不错的改进,也因为算法收敛得非常快在答案中,如果您出于任何原因必须用笔和纸计算平方根,这是一个很好的算法。

(原始编辑)

static class MyMath
{
    public static double Sqrt(double _d)
    {
        double w = _d;
        double h = 1;
        double t = 0;
        if (h > w)
        {
            h = _d;
            w = 1;
        }
        while(true)
        {
            if (w < h)
            {
                break;
            }
            w *= 0.5;
            h += h;
        }
        for (int i = 0; i < 5; i++)
        {
            t = ((w + h) * 0.5);
            h = ((h / t) * w);
            w = t;
        }
        return (t);
    }
}

编辑: 添加了if (h &gt; w) 以提高小于 1 的平方数时的精度,并将 for 循环从 6 减少到 5。

2019 年编辑: 稍微更新的版本,它快了 24% (然后是我原来的编辑)

   public static double Sqrt(double _d)
   {
       double w = _d, h = 1, t = 0;
       if (w < 1)
       {
           h = _d;
           w = 1;
       }
       do
       {
           w *= 0.5;
           h += h;
       } while (w > h);
       for (int i = 0; i < 4; i++)
       {
           t = ((w + h) * 0.5);
           h = ((h / t) * w);
           w = t;
       }
       return (((w + h) * 0.5));
   }

我最快的版本,它快了 51% (然后是我的原始编辑),总共只使用了两个部门,但不是干净的代码:( Math.Sqrt() 仍然快 4 倍左右)

  public static double Sqrt(double _d) 
  {
      double w = _d, h = 1;

      if (w < 1)
      {
          h = _d;
          w = 1;
      }

      do
      {
          w *= 0.5;
          h += h;
      } while (w > h);

      double x = h + w;
      double x2 = x * x;
      double x4 = x2 * x * x;
      double x6 = x4 * x * x;
      double x8 = x6 * x * x;
      double h2 = h * h;
      double h3 = h2 * h;
      double h4 = h3 * h;
      double w2 = w * w;
      double w3 = w2 * w;
      double w4 = w3 * w;
      double hw = h * w;
      double h2w2 = h2 * w2;
      double a = (256 * h4 * w4 + 1792 * h3 * w3 * x2 + 1120 * h2w2 * x4 + 112 * hw * x6 + x8);
      double b = (16 * h2w2 + 24 * hw * x2 + x4);
      double c = (4 * hw + x2);
      double xcb = x * c * b;
      return (8 * hw * xcb) / a + a / (32 * xcb);
  }

2019 年第 2 版:

这是我创建的一个新算法,它通过将 y = x^2 图形负偏移 "a"(到 sqrt 的数量) 然后 x 轴与图形相交的位置是精确的平方根,并且该算法试图通过在最小最大边界之间创建一条线来找到该交互点,然后从该线与 x.axis 相交的位置创建两个新的最小最大边界并重复,它将很快收敛到完整的双精度精度平方根,通常在 10 次迭代内。 这是一个紧凑的算法,只需很少的迭代即可收敛到非常高的精度,但不幸的是它比我的其他算法慢了一点(慢了 10 倍),但我仍然认为这是一个有趣的算法。

public static double Sqrt3(double a)
{
    double b = a + 1, c = 0, d = -a, e = b * b, f;
    g: f = (c * e - d * b) / (e - d);
    if (double.IsNaN(f)) return c;
    d = f * f - a;
    b = (f + c) * 0.5;
    e = b * b - a;
    c = f;
    goto g;
}
/*
    Sqrt(3)
    0 : 0.631578947368421
    1 : 3.37719298245614
    2 : 1.81530333576402
    3 : 1.74835949499074
    4 : 1.73228078277221
    5 : 1.7320513552106
    6 : 1.7320508075871
    7 : 1.73205080756888
    8 : 1.73205080756888
    9 : 1.73205080756888
    1.7320508075688772 : My.Sqrt() .ToString("G17")
    1.7320508075688772 : Math.Sqrt() .ToString("G17")
*/

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多