【问题标题】:Best practice with Math.PowMath.Pow 的最佳实践
【发布时间】:2011-03-04 10:09:22
【问题描述】:

我正在开发一个扩展 OpenCV、HALCON、... 的图像处理库。该库必须与 .NET Framework 3.5 一起使用,由于我对 .NET 的经验有限,我想就性能提出一些问题。

我遇到了一些具体的事情,我无法正确地向自己解释,希望您问一下 a) 为什么以及 b) 处理这些案件的最佳做法是什么。

我的第一个问题是关于 Math.pow。我已经在 StackOverflow 上找到了一些答案,这很好地解释了它(a),但不知道该怎么做(b)。我的基准程序如下所示

Stopwatch watch = new Stopwatch();  // from the Diagnostics class
watch.Start();
for (int i = 0; i < 1000000; i++)
    double result = Math.Pow(4,7)   // the function call
watch.Stop()

结果不是很好(在我的电脑上大约 300 毫秒)(我已经运行了 10 次测试并计算了平均值)。

我的第一个想法是检查这是否是因为它是一个静态函数。所以我实现了自己的类

class MyMath
{
    public static double Pow (double x, double y)   //Using some expensive functions to calculate the power
    {
        return Math.Exp(Math.Log(x) * y);
    }

    public static double PowLoop (double x, int y)  // Using Loop
    {
        double res = x;
        for(int i = 1; i < y; i++)
            res *= x;
        return res;
    }

    public static double Pow7 (double x)            // Using inline calls
    {
        return x * x * x * x * x * x * x;
    }
}

我检查的第三件事是我是否将 Math.Pow(4,7) 直接替换为 4*4*4*4*4*4*4。

结果是(10 次测试运行的平均值)

300 ms   Math.Pow(4,7)
356 ms   MyMath.Pow(4,7)    //gives wrong rounded results
264 ms   MyMath.PowLoop(4,7)
 92 ms   MyMath.Pow7(4)
 16 ms   4*4*4*4*4*4*4

现在我现在的情况基本上是这样的:不要用数学来做 Pow。我唯一的问题是……我现在真的必须实现自己的数学课吗?仅仅为 power 函数实现一个自己的类似乎有点无效。 (顺便说一句。PowLoop 和 Pow7 在 Release 版本中甚至快了约 25%,而 Math.Pow 则不然)。

所以我最后的问题是

a) 如果我根本不使用 Math.Pow(但可能是分数)(这让我有点难过),那我错了。

b) 如果你有代码要优化,你真的是直接写所有这些数学运算吗?

c) 可能已经有一个更快的(开源^^)数学运算库

d) 我的问题的来源基本上是:我假设 .NET Framework 本身已经为这些基本操作提供了非常优化的代码/编译结果 - 无论是 Math-Class 还是处理数组,我有点惊讶通过编写自己的代码,我将获得多少好处。在我不能直接信任 C# 的 C# 中是否有一些其他的通用“字段”或其他东西需要注意。

【问题讨论】:

  • 我认为 4*4*4*4*4*4*4 将在编译时进行评估,因此它的速度非常快。
  • 我认为对于大多数人来说,100 毫秒左右并不是什么大问题。对于大多数人来说,C# 通常不是此类应用程序的首选。
  • 你测试过更大的数字吗?我认为 Math.Pow 针对更大的指数进行了优化,并且执行以下操作:x^7 == x^{3+3+1} == {x ^ 3 + x^3 x},这意味着它正在运行顺序大约是 O(log(n) ),而您的解决方案是 O(n),对于大指数来说可能会慢得多。
  • 两个最终测试(Pow7 和静态)都是编译器可优化的,因此不像以前那样通用。
  • Math.Pow 确实应该有一个重载,它需要一个 int 指数,因为这是最常见的情况,它会运行得更快。

标签: c# .net pow


【解决方案1】:

要记住两件事:

  1. 你可能don't need to optimise这段代码。您刚刚在不到一秒的时间内对该函数进行了一百万次调用。这真的会在您的程序中造成大问题吗?

  2. Math.Pow 无论如何可能是相当理想的。猜测一下,它将调用一个用较低级别语言编写的适当的数字库,这意味着您不应该期望数量级的增加。

  3. 数值编程比你想象的要难。即使是你认为你知道如何计算的算法,也不是这样计算的。例如,当您计算平均值时,您不应该只是将数字相加并除以您拥有的数字。 (现代数值库使用两遍例程来纠正浮点错误。)

也就是说,如果您确定确实需要优化,那么请考虑使用整数而不是浮点值,或者将其外包给另一个数值库。

【讨论】:

    【解决方案2】:

    首先,整数运算比浮点运算快很多。如果您不需要浮点值,请不要使用浮点数据类型。这对于任何编程语言都是如此。

    其次,正如您自己所说,Math.Pow 可以处理实数。它使用比简单循环更复杂的算法。难怪它比简单的循环慢。如果您摆脱循环并只进行 n 次乘法,那么您也将减少设置循环的开销 - 从而使其更快。但是如果你不使用循环,你必须知道 预先指定指数的值 - 它不能在运行时提供。

    我不太确定为什么 Math.ExpMath.Log 更快。但是如果你用Math.Log,你就找不到负值的威力了。

    基本上int 更快,避免循环避免额外开销。但是,当您选择这些时,您会牺牲一些灵活性。但是,当您只需要整数时避免使用实数通常是一个好主意,但是在这种情况下,当已经存在一个自定义函数时编写一个自定义函数似乎有点太多了。

    你必须问自己的问题是这是否值得。 Math.Pow 真的会减慢你的程序吗?无论如何,已经与您的语言捆绑在一起的Math.Pow 通常是最快的或非常接近的。如果您真的想创建一个真正通用的替代实现(即不仅限于整数、正值等),您最终可能会使用默认实现中使用的相同算法。

    【讨论】:

    • 虽然我同意您回答的一般观点,但我想说一句:当然库函数功能已优化,但并非总是可以针对所有情况进行优化。考虑例如排序,其中有很多有效的算法选项,这实际上取决于您的特定数据,哪一个是最好的。
    • @markijbema:这就是我在回答中使用“通用”一词的原因。如果您可以对数据做出额外的假设(例如,它们将始终是整数,或者在给定范围内),那么您当然可以使用该数据进行优化(例如计数排序而不是快速排序)。这就是我回答的重点,对于通用功能,很难匹配库。
    【解决方案3】:

    当您谈到对一行代码进行一百万次迭代时,显然每个小细节都会产生影响。

    Math.Pow() 是一个函数调用,它比手动 4*4...*4 示例要慢得多。

    不要编写自己的类,因为它怀疑你是否能够编写比标准数学类更优化的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-08
      • 2014-10-08
      • 2014-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-05
      相关资源
      最近更新 更多