【问题标题】:Is there any benefit to C#'s Math.Max() implementation vs doing it mathemathicaly? [closed]C# 的 Math.Max() 实现与数学上的实现相比有什么好处吗? [关闭]
【发布时间】:2021-05-11 07:00:06
【问题描述】:

在 C# 中 Math.Max() 被实现为:

   [NonVersionable]
   public static int Max(int val1, int val2)
   {
       return (val1 >= val2) ? val1 : val2;
   }

虽然我相信它也可以实现为:

C# 的实现是否有任何好处,例如在数学上实现它的效率?

(a + b + Math.Abs(a-b)) /2

【问题讨论】:

  • 如果他们达到相同的结果,有什么区别? Performance?你衡量绩效了吗?为什么要重新发明轮子?
  • 如果您关心性能,您是否尝试过基准测试
  • 另外,在处理浮点值时,数学方法会导致精度损失。
  • 如果你确实有正确的代码,由于分支预测,它可能比官方版本更快。但正如其他人所提到的,正确的代码很困难
  • 实际的“Math.Max”实现简单、直接且无错误。所谓的 mathematical 实现显然不是 max 的实现(它是,但读者必须考虑它)。问题是,为什么你会用这个公式来扭曲你的想法,以获得像 max 这样的琐事?那是因为数学家不喜欢使用条件函数,他们写作和学习都很烦人。因此,如果他们能找到在线人,即使是虚假陈述,他们也会使用它。 'abs(x) = sqrt(x²)' 同样适用。

标签: c#


【解决方案1】:

一方面,数学实现是不正确的,因为它不处理上溢/下溢。

如果你这样做:

// suppose Max is implemented the "mathematical" way
Max(int.MaxValue, int.MaxValue - 1)

它会给出-1。

【讨论】:

    【解决方案2】:

    假设您的数学是正确的,并且您可以按照您的建议实施它。它仍然有零到没有好处,因为在这两种情况下时间复杂度都是 O(1)。但我认为 C# 中的实现更具可读性。此外,Math.Abs​​ 函数的实现可能类似于 return x > 0 ? x : -x;,这将与当前实现中的条件数量相同。

    【讨论】:

    • 你没有考虑到if版本的分支预测错误
    • @Charlieface - 考虑到Math.Abs 可能有一个分支
    • @MartinSmith 是的,我只是在你发帖之前查了一下。是和不是。这个答案根本没有真正讨论它,您也许可以通过推断来解决它,即根据定义它必须更慢。如果我们不允许MaxValue 的边缘情况,我们可以将Abs 优化为return 0x7FFFFFFF & val
    • @MartinSmith 抱歉在这里正确执行stackoverflow.com/a/2074403/14868997
    【解决方案3】:

    另一个原因取决于您的 CPU。 CISC CPU 非常喜欢有一个最大数学运算符。以 x64 中的 MAXSS 为例。

    编译器可能能够弄清楚您要做什么。但有了必要的变通办法,这似乎不太可能。允许将您的意图下推到堆栈中,从而使您的代码最有可能解析为单个 CPU 操作。我实际上对@Luuk 的基准测试结果感到惊讶。但它会显示,优化可读性,但当你关心性能时,请确保你彻底测量。事实上,我总是尝试将性能测试留在单元测试中,随着编译器的改进,b/c 事情会发生变化。

    【讨论】:

      【解决方案4】:

      有一条关于基准测试的评论,还有一条关于上溢/下溢的评论。

      此代码表明数学方法更快,即使对溢出进行简单的解决方法

          int a = 1;
          int b = Int32.MaxValue - 1;
          int count = 100000;
          DateTime start;
          Console.WriteLine($"a:{a} b:{b}");
      
          Console.WriteLine("Method1");
          start = DateTime.Now;
          for (int i=1; i< count; i++)
          {
              int c = Math.Max(a, b);
              //if (i == 1) Console.Write($"{c} ");
          }
          Console.WriteLine($"time: { (DateTime.Now - start).TotalMilliseconds }");
      
      
          Console.WriteLine("Method2");
          start = DateTime.Now;
          for (int i = 1; i < count; i++)
          {
              int c = Convert.ToInt32((double)(((double)a + b + Math.Abs(a - b)) / 2.0));
              //if(i == 1) Console.Write($"{c} ");
          }
          Console.WriteLine($"time: { (DateTime.Now - start).TotalMilliseconds }");
      

      输出:

      a:1 b:2147483646
      Method1
      time: 3,0267
      Method2
      time: 0,4752
      Press any key to continue . . .
      

      编辑:建议使用 benchmark.net

      private readonly int a = 1;
      private readonly int b = Int32.MaxValue - 1;
      
      public Max()
      {
          a = new Random(42).Next();
          b = new Random(24).Next();
      }
      
      [Benchmark]
      public  int Method1() => Math.Max(a, b);
      
      [Benchmark]
      public int Method2() => Convert.ToInt32((double)(((double)a + b + Math.Abs(a - b)) / 2.0));
      
      [Benchmark]
      public int Method3() => ((a + b + Math.Abs(a - b) / 2));
      
      [Benchmark]
      public int Method4() => ((a >= b) ? a :b);
      

      结果:

      // * Summary *
      
      BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
      Intel Core i7-8700K CPU 3.70GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
      .NET Core SDK=5.0.102
        [Host]     : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
        DefaultJob : .NET Core 5.0.2 (CoreCLR 5.0.220.61120, CoreFX 5.0.220.61120), X64 RyuJIT
      
      
      |  Method |      Mean |     Error |    StdDev |
      |-------- |----------:|----------:|----------:|
      | Method1 | 0.2288 ns | 0.0058 ns | 0.0054 ns |
      | Method2 | 2.7820 ns | 0.0157 ns | 0.0123 ns |
      | Method3 | 0.4558 ns | 0.0094 ns | 0.0088 ns |
      | Method4 | 0.2645 ns | 0.0133 ns | 0.0124 ns |
      
      // * Hints *
      Outliers
        Max.Method2: Default -> 3 outliers were removed (4.21 ns..4.23 ns)
      
      // * Legends *
        Mean   : Arithmetic mean of all measurements
        Error  : Half of 99.9% confidence interval
        StdDev : Standard deviation of all measurements
        1 ns   : 1 Nanosecond (0.000000001 sec)
      
      // ***** BenchmarkRunner: End *****
      // ** Remained 0 benchmark(s) to run **
      Run time: 00:02:02 (122.05 sec), executed benchmarks: 4
      
      Global total time: 00:02:05 (125.55 sec), executed benchmarks: 4
      

      【讨论】:

      • “这段代码表明数学方法更快” - 不是以任何严格的方式。我强烈建议使用benchmark.net,而不是像这样手动滚动微基准。 (您可能会得到相同的结果 - 但将其显示为 benchmark.net 示例会更加引人注目。)
      • 这看起来不是一个好的基准 - 颠倒这两种方法的测试顺序会改变我的结果
      • 在考虑基准测试之前先使用Stopwatch
      • @Luuk:您测量的时间很小 - 所以不,那是安全的。例如,如果您所观察到的基本上是在第一个循环中重新启动,我不会感到惊讶。微基准测试很难,这就是 benchmark.net 存在的原因。
      • 您修改后的测试是否与“数学方法更快”的断言相矛盾? 0.2288ns 平均 vs 0.4558ns?
      猜你喜欢
      • 2011-01-12
      • 2011-05-09
      • 2011-03-11
      • 1970-01-01
      • 2012-03-17
      • 2010-09-13
      • 1970-01-01
      • 2010-09-07
      • 1970-01-01
      相关资源
      最近更新 更多