Math.Max vs inline if - 有什么区别?

che*_*525 31 c# inline-if

我今天正在研究一个项目,发现自己在几个地方使用Math.Max,并在其他地方使用内联if语句.所以,我想知道是否有人知道哪个更"好"......或者更确切地说,真正的差异是什么.

例如,在下面,c1 = c2:

Random rand = new Random();
int a = rand.next(0,10000);
int b = rand.next(0,10000);

int c1 = Math.Max(a, b);
int c2 = a>b ? a : b;
Run Code Online (Sandbox Code Playgroud)

我是专门询问C#,但我想不同语言的答案可能会有所不同,但我不确定哪些有类似的概念.

Rio*_*ams 31

我立即注意到的一个主要差异是为了可读性,据我所知,为了实现/性能,它们几乎是等价的.

Math.Max(a,b) 无论以前的编码知识如何,都很容易理解.

a>b ? a : b 将要求用户至少具有三元运算符的一些知识.

" 如有疑问 - 请寻求可读性 "

  • 显然它需要一些知识.但我相信它比这里的替代方案更直接. (3认同)
  • 需要一些知识吗?为什么有人不知道三元运算符是什么 - 甚至会尝试阅读您的代码? (2认同)

Lui*_*rez 22

我认为在这个讨论中加入一些数字会很有趣,所以我写了一些代码来描述它.正如所料,它们在所有实际用途中几乎相同.

代码完成了十亿次循环(是10亿).减去循环的开销,你会得到:

  • Math.Max()耗时.0044秒,运行10亿次
  • 内联如果花费.0055秒运行10亿次

我通过运行10亿次空循环减去了我计算的开销,开销是1.2秒.

我在笔记本电脑上运行了这个,64位Windows 7,3.3 Ghz Intel Core i5(U470).代码是在发布模式下编译的,并且在没有附加调试器的情况下运行.

这是代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace TestMathMax {
    class Program {
        static int Main(string[] args) {
            var num1 = 10;
            var num2 = 100;
            var maxValue = 0;
            var LoopCount = 1000000000;
            double controlTotalSeconds;
            { 
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (var i = 0; i < LoopCount; i++) {
                    // do nothing
                }
                stopwatch.Stop();
                controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
                Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = Math.Max(num1, num2);
                }
                stopwatch.Stop();
                Console.WriteLine("Math.Max() - " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }
            Console.WriteLine();
            {
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                for (int i = 0; i < LoopCount; i++) {
                    maxValue = num1 > num2 ? num1 : num2;
                }
                stopwatch.Stop();
                Console.WriteLine("Inline Max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
                Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
            }

            Console.ReadLine();

            return maxValue;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更新结果2015年2月7日

在Windows 8.1,Surface 3 Pro,i7 4650U 2.3Ghz Ran上作为控制台应用程序在发布模式下没有附加调试器.

  • Math.Max() - 0.3194749秒
  • 内联最大值:0.3465041秒

  • 如果将num1,num2和maxValue转换为double,结果会大不相同.因为我的结果几乎与@luisperezphd完全相同,但是一旦转换为双打,Math.Max就会上升6秒.这是一个巨大的差异,我跑了几次只是为了确保. (4认同)

小智 14

如果声明被认为有益

摘要

表单声明if (a > max) max = a是确定一组数字最大值的最快方法.然而,循环基础结构本身占用了大部分CPU时间,因此这种优化最终是值得怀疑的.

细节

luisperezphd的答案很有意思,因为它提供了数字,但我相信这个方法存在缺陷:编译器很可能将比较移出循环,所以答案并不能衡量它想要衡量的内容.这解释了控制回路和测量回路之间可忽略不计的时序差异.

为了避免这种循环优化,我添加了一个依赖于循环变量的操作,空控制循环以及所有测量循环.我模拟了在数字列表中找到最大值的常见用例,并使用了三个数据集:

  • 最佳情况:第一个数字是最大值,后面的所有数字都是较小的
  • 最坏的情况:每个数字都比前一个更大,所以最大值会改变每次迭代
  • 平均情况:一组随机数

请参阅下面的代码.

结果对我来说相当令人惊讶.在我的Core i5 2520M笔记本电脑上,我获得了以下10亿次迭代(空控制在所有情况下大约需要2.6秒):

  • max = Math.Max(max, a):2.0秒最佳情况/ 1.3秒最差情况/ 2.0秒平均情况
  • max = Math.Max(a, max):1.6秒最佳情况/ 2.0秒最差情况/ 1.5秒平均情况
  • max = max > a ? max : a:1.2秒最佳情况/ 1.2秒最坏情况/ 1.2秒平均情况
  • if (a > max) max = a:0.2秒最佳情况/ 0.9秒最差情况/ 0.3秒平均情况

因此,尽管拥有较长的CPU流水线以及由此导致的分支处罚,但旧的if陈述仍然是所有模拟数据集的明显优势; 在最好的情况下,它比它快10倍Math.Max,在最坏的情况下仍然快30%以上.

令人惊讶的是,争论的顺序是Math.Max重要的.据推测,这是因为CPU分支预测逻辑对两种情况的工作方式不同,并且根据参数的顺序或多或少地错误预测分支.

但是,大部分CPU时间都花在了循环基础结构上,因此最终这种优化充其量是有问题的.它提供了可测量但略微减少的总体执行时间.

由luisperezphd更新

我不能把它作为评论,在这里写它更有意义,而不是作为我答案的一部分,所以它在上下文中.

你的理论是有道理的,但我无法重现结果.首先出于某种原因使用你的代码我的控制循环比包含工作的循环花费更长的时间.

出于这个原因,我将这里的数字相对于最低时间而不是控制循环.结果中的秒数是最长时间所花费的时间.例如,在紧接着最快的时间下面的结果是Math.Max(a,max)的最佳情况,所以每个其他结果表示他们花了多长时间.

以下是我得到的结果:

  • max = Math.Max(max, a):0.012秒最佳情况/ 0.007秒最差情况/ 0.028秒平均情况
  • max = Math.Max(a, max):0.000最佳情况/ 0.021最坏情况/ 0.019秒平均情况
  • max = max > a ? max : a:0.022秒最佳情况/ 0.02秒最差情况/ 0.01秒平均情况
  • if (a > max) max = a:0.015秒最佳情况/ 0.024秒最差情况/ 0.019秒平均情况

我第二次跑它时得到了:

  • max = Math.Max(max, a):0.024秒最佳情况/ 0.010秒最差情况/ 0.009秒平均情况
  • max = Math.Max(a, max):0.001秒最佳情况/ 0.000秒最差情况/ 0.018秒平均情况
  • max = max > a ? max : a:0.011秒最佳情况/ 0.005秒最差情况/ 0.018秒平均情况
  • if (a > max) max = a:0.000秒最佳情况/ 0.005秒最坏情况/ 0.039秒平均情况

在这些测试中有足够的音量可以消除任何异常.尽管如此,结果却截然不同.也许数组的大内存分配与它有关.或者可能差异太小,以至于当时计算机上发生的任何其他事情都是变异的真正原因.

注意最快的时间,在上面的结果中表示0.000约为8秒.因此,如果你认为最长的运行时间是8.039,那么时间的变化大约是0.5%(也就是说太小了).

电脑

代码在Windows 8.1,i7 4810MQ 2.8Ghz上运行,并在.NET 4.0中编译.

代码修改

我稍微修改了你的代码,以上面显示的格式输出结果.在开始考虑运行程序集时.NET可能需要的任何额外加载时间后,我还添加了额外的代码等待1秒.

此外,我运行了两次所有测试以解决任何CPU优化问题.最后我将intfor 更改为ia unit所以我可以运行循环40亿次而不是10亿次以获得更长的时间跨度.

这可能都是矫枉过正,但要尽可能确保测试不会受到任何这些因素的影响.

您可以在http://pastebin.com/84qi2cbD找到该代码

using System;
using System.Diagnostics;

namespace ProfileMathMax
{
  class Program
  {
    static double controlTotalSeconds;
    const int InnerLoopCount = 100000;
    const int OuterLoopCount = 1000000000 / InnerLoopCount;
    static int[] values = new int[InnerLoopCount];
    static int total = 0;

    static void ProfileBase()
    {
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        int maxValue;
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                // baseline
                total += values[i];
            }
        }
        stopwatch.Stop();
        controlTotalSeconds = stopwatch.Elapsed.TotalSeconds;
        Console.WriteLine("Control - Empty Loop - " + controlTotalSeconds + " seconds");
    }

    static void ProfileMathMax()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(values[i], maxValue);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(a, max) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileMathMaxReverse()
    {
        int maxValue;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = Math.Max(maxValue, values[i]);
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("Math.Max(max, a) - " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileInline()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                maxValue = maxValue > values[i] ? values[i] : maxValue;
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("max = max > a ? a : max: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void ProfileIf()
    {
        int maxValue = 0;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        for (int j = 0; j < OuterLoopCount; j++)
        {
            maxValue = 0;
            for (int i = 0; i < InnerLoopCount; i++)
            {
                if (values[i] > maxValue)
                    maxValue = values[i];
                total += values[i];
            }
        }
        stopwatch.Stop();
        Console.WriteLine("if (a > max) max = a: " + stopwatch.Elapsed.TotalSeconds + " seconds");
        Console.WriteLine("Relative: " + (stopwatch.Elapsed.TotalSeconds - controlTotalSeconds) + " seconds");
    }

    static void Main(string[] args)
    {
        Random rnd = new Random();
        for (int i = 0; i < InnerLoopCount; i++)
        {
            //values[i] = i;  // worst case: every new number biggest than the previous
            //values[i] = i == 0 ? 1 : 0;  // best case: first number is the maximum
            values[i] = rnd.Next(int.MaxValue);  // average case: random numbers
        }

        ProfileBase();
        Console.WriteLine();
        ProfileMathMax();
        Console.WriteLine();
        ProfileMathMaxReverse();
        Console.WriteLine();
        ProfileInline();
        Console.WriteLine();
        ProfileIf();
        Console.ReadLine();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个真正的科学答案。但是你忘记了最终结论,这是一篇真正的科学文章的典型特征。:) (2认同)

dth*_*rpe 6

如果JITer选择内联Math.Max函数,则可执行代码将与if语句相同.如果没有内联Math.Max,它将作为函数调用执行,并且if语句中不存在调用和返回开销.因此,if语句在内联情况下会给Math.Max()提供相同的性能,或者if语句在非内联情况下可能会快几个时钟周期,但除非你运行数十,否则差异不会很明显数以百万计的比较.

由于两者之间的性能差异很小,在大多数情况下可以忽略不计,我更喜欢Math.Max(a,b),因为它更容易阅读.


ICR*_*ICR 6

我会说更快地理解Math.Max正在做什么,而这应该是这里唯一的决定因素.

但作为一种放纵,有趣的是考虑Math.Max(a,b)一次评估参数,同时a > b ? a : b评估其中一个两次.局部变量不是问题,但对于有副作用的属性,副作用可能会发生两次.


xno*_*nor 6

数学.Max(a,b)

并不等于a > b ? a : b在所有情况下。

Math.Max返回两个参数中较大的值,即:

if (a == b) return a; // or b, doesn't matter since they're identical
else if (a > b && b < a) return a;
else if (b > a && a < b) return b;
else return undefined;
Run Code Online (Sandbox Code Playgroud)

例如,double.NaN在双重载的情况下,未定义被映射到。Math.Max

a > b ? 甲:乙

如果 a 大于 b,则计算结果为 a,这并不一定意味着 b 小于 a。

一个简单的例子证明它们是不等价的:

var a = 0.0/0.0; // or double.NaN
var b = 1.0;
a > b ? a : b // evaluates to 1.0
Math.Max(a, b) // returns double.NaN
Run Code Online (Sandbox Code Playgroud)