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

时间:2011-03-29 21:01:54

标签: 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;

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

8 个答案:

答案 0 :(得分:31)

我立即注意到的一个主要差异是出于可读性的考虑,据我所知,在实现/性能方面,它们几乎相当。

无论以前的编码知识如何,

Math.Max(a,b)都很容易理解。

a>b ? a : b要求用户至少了解三元运算符。

如有疑问 - 请提高可读性

答案 1 :(得分:21)

我认为在这个讨论中加入一些数字会很有趣,所以我写了一些代码来描述它。正如所料,它们几乎完全符合所有实际目的。

代码完成了十亿次循环(是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;
        }
    }
}

2015年2月7日更新结果

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

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

答案 2 :(得分:13)

如果陈述被认为是有益的

摘要

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语句是所有模拟数据集的明显优势;在最好的情况下,它比Math.Max快10倍,在最坏的情况下仍然快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%(0.5%) - 也就是说太小了。

电脑

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

代码修改

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

此外,我还运行了所有测试两次,以解决任何CPU优化问题。最后,我将int的{​​{1}}更改为i,这样我就可以运行40亿次而不是10亿次来获得更长的时间。

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

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

代码

unit

答案 3 :(得分:6)

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

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

答案 4 :(得分:6)

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

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

答案 5 :(得分:2)

关于性能,Modern CPU具有内部命令管道,使得每个汇编命令都在几个内部步骤中执行。 (例如取,解释,计算,存储)

在大多数情况下,CPU足够智能,可以并行执行这些步骤以实现顺序命令,因此整体吞吐量非常高。

这是好的,直到有一个分支(if?:等)。 分支可能会破坏序列并强制CPU废弃管道。 这会花费很多时钟周期。

理论上,如果编译器足够智能,可以使用内置的CPU命令实现Math.Max,并且可以避免分支。

在这种情况下,Math.Max实际上会比if更快 - 但它取决于编译器..

如果更复杂的Max - 就像处理向量一样,double []v; v.Max()编译器可以使用高度优化的库代码,这比常规编译代码快得多。

所以最好使用Math.Max,但是如果它足够重要,也建议检查你的特定目标系统和编译器。

答案 6 :(得分:2)

Math.Max(A,B)

在所有情况下,

不等同于#!/bin/sh -x PATH="/opt/433Utils/RPi_utils:$PATH" if [ "$1" = "A" ]; then case "$2" in off|OFF|0) arg=1364 ;; *) arg=1361 ;; esac codesend "$arg" fi

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; 出现双重超载,则undefined将映射到double.NaN

a&gt; b? a:b

计算a a是否大于b,这并不一定意味着b小于a。

一个简单的例子,证明它们不等同:

Math.Max

答案 7 :(得分:0)

进行操作; N必须为> = 0

一般解决方案:

A) N = Math.Max(0, N)
B) if(N < 0){N = 0}

按速度排序:

慢:数学最大值(A)<(B)if-then语句:快(比解决方案“ A”快3%)

但是我的解决方案比解决方案“ B”快4%:

N *= Math.Sign(1 + Math.Sign(N));