我今天正在开展一个项目,发现自己在几个地方使用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#,但我想不同语言的答案可能会有所不同,但我不确定哪些有相似的概念。
答案 0 :(得分:31)
我立即注意到的一个主要差异是出于可读性的考虑,据我所知,在实现/性能方面,它们几乎相当。
无论以前的编码知识如何, Math.Max(a,b)
都很容易理解。
a>b ? a : b
要求用户至少了解三元运算符。
“如有疑问 - 请提高可读性”
答案 1 :(得分:21)
我认为在这个讨论中加入一些数字会很有趣,所以我写了一些代码来描述它。正如所料,它们几乎完全符合所有实际目的。
代码完成了十亿次循环(是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上 在没有附带调试器的发布模式下作为控制台应用程序运行。
答案 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时间都花在了循环基础架构上,因此最终这种优化最多也是值得怀疑的。它可以在整体执行时间内实现可测量但略微减少。
我无法将其作为评论,因此将其写在此处而不是作为答案的一部分更有意义,以便它在上下文中。
你的理论是有道理的,但我无法重现结果。首先出于某种原因使用你的代码,我的控制循环所花费的时间比包含工作的循环要长。
出于这个原因,我在这里将数字相对于最低时间而不是控制循环。结果中的秒数是最长时间所花费的时间。例如,在紧接着最快的时间下面的结果是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)
不等同于至#!/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 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));