IF是否比IF-ELSE更好?
哪一个代码块性能更好,哪一个更具可读性? 我想这个收益可以忽略不计,特别是在第二块。 我只是好奇。
块#1
string height; string width; if (myFlag == 1) { height = "60%"; width = "60%"; } else { height = "80%"; width = "80%"; }
块#2
string height = "80%"; string width = "80%"; if (myFlag == 1) { height = "60%"; width = "60%"; }
更新
当我testing上述代码的结果是,两个块执行相同的
块#1
myFlag = 1: 3 Milliseconds myFlag = 0: 3 Milliseconds
块#2
myFlag = 1: 3 Milliseconds myFlag = 0: 3 Milliseconds
但是我在这里注意到的一个重要的事情(感谢Matthew Steeples在这里回答 )是因为我testing过的代码块没有使用variables高度和宽度,除了在if-else中的赋值以及如果代码块1和2分别, 编译器通过完全删除if和if-else块来优化IL代码,因此在这里显示的testing结果是无效的 。
我已经更新了两个代码块,将高度和宽度的值写入文件,从而再次使用它们,强制编译器运行我们的testing块(我希望),但是您可以从代码中观察到实际的文件写入部分不影响我们的testing结果
这是更新的结果,C#和IL代码
结果
块#1
myFlag = 1: 1688 Milliseconds myFlag = 0: 1664 Milliseconds
块#2
myFlag = 1: 1700 Milliseconds myFlag = 0: 1677 Milliseconds
C#.net代码
块#1
public long WithIfAndElse(int myFlag) { Stopwatch myTimer = new Stopwatch(); string someString = ""; myTimer.Start(); for (int i = 0; i < 1000000; i++) { string height; string width; if (myFlag == 1) { height = "60%"; width = "60%"; } else { height = "80%"; width = "80%"; } someString = "Height: " + height + Environment.NewLine + "Width: " + width; } myTimer.Stop(); File.WriteAllText("testifelse.txt", someString); return myTimer.ElapsedMilliseconds; }
块#2
public long WithOnlyIf(int myFlag) { Stopwatch myTimer = new Stopwatch(); string someString = ""; myTimer.Start(); for (int i = 0; i < 1000000; i++) { string height = "80%"; string width = "80%"; if (myFlag == 1) { height = "60%"; width = "60%"; } someString = "Height: " + height + Environment.NewLine + "Width: " + width; } myTimer.Stop(); File.WriteAllText("testif.txt", someString); return myTimer.ElapsedMilliseconds; }
IL代码由ildasm.exe生成
块#1
.method public hidebysig instance int64 WithIfAndElse(int32 myFlag) cil managed { // Code size 144 (0x90) .maxstack 3 .locals init ([0] class [System]System.Diagnostics.Stopwatch myTimer, [1] string someString, [2] int32 i, [3] string height, [4] string width, [5] string[] CS$0$0000) IL_0000: newobj instance void [System]System.Diagnostics.Stopwatch::.ctor() IL_0005: stloc.0 IL_0006: ldstr "" IL_000b: stloc.1 IL_000c: ldloc.0 IL_000d: callvirt instance void [System]System.Diagnostics.Stopwatch::Start() IL_0012: ldc.i4.0 IL_0013: stloc.2 IL_0014: br.s IL_0070 IL_0016: ldarg.1 IL_0017: ldc.i4.1 IL_0018: bne.un.s IL_0029 IL_001a: ldstr "60%" IL_001f: stloc.3 IL_0020: ldstr "60%" IL_0025: stloc.s width IL_0027: br.s IL_0036 IL_0029: ldstr "80%" IL_002e: stloc.3 IL_002f: ldstr "80%" IL_0034: stloc.s width IL_0036: ldc.i4.5 IL_0037: newarr [mscorlib]System.String IL_003c: stloc.s CS$0$0000 IL_003e: ldloc.s CS$0$0000 IL_0040: ldc.i4.0 IL_0041: ldstr "Height: " IL_0046: stelem.ref IL_0047: ldloc.s CS$0$0000 IL_0049: ldc.i4.1 IL_004a: ldloc.3 IL_004b: stelem.ref IL_004c: ldloc.s CS$0$0000 IL_004e: ldc.i4.2 IL_004f: call string [mscorlib]System.Environment::get_NewLine() IL_0054: stelem.ref IL_0055: ldloc.s CS$0$0000 IL_0057: ldc.i4.3 IL_0058: ldstr "Width: " IL_005d: stelem.ref IL_005e: ldloc.s CS$0$0000 IL_0060: ldc.i4.4 IL_0061: ldloc.s width IL_0063: stelem.ref IL_0064: ldloc.s CS$0$0000 IL_0066: call string [mscorlib]System.String::Concat(string[]) IL_006b: stloc.1 IL_006c: ldloc.2 IL_006d: ldc.i4.1 IL_006e: add IL_006f: stloc.2 IL_0070: ldloc.2 IL_0071: ldc.i4 0xf4240 IL_0076: blt.s IL_0016 IL_0078: ldloc.0 IL_0079: callvirt instance void [System]System.Diagnostics.Stopwatch::Stop() IL_007e: ldstr "testifelse.txt" IL_0083: ldloc.1 IL_0084: call void [mscorlib]System.IO.File::WriteAllText(string, string) IL_0089: ldloc.0 IL_008a: callvirt instance int64 [System]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() IL_008f: ret } // end of method frmResearch::WithIfAndElse
块#2
.method public hidebysig instance int64 WithOnlyIf(int32 myFlag) cil managed { // Code size 142 (0x8e) .maxstack 3 .locals init ([0] class [System]System.Diagnostics.Stopwatch myTimer, [1] string someString, [2] int32 i, [3] string height, [4] string width, [5] string[] CS$0$0000) IL_0000: newobj instance void [System]System.Diagnostics.Stopwatch::.ctor() IL_0005: stloc.0 IL_0006: ldstr "" IL_000b: stloc.1 IL_000c: ldloc.0 IL_000d: callvirt instance void [System]System.Diagnostics.Stopwatch::Start() IL_0012: ldc.i4.0 IL_0013: stloc.2 IL_0014: br.s IL_006e IL_0016: ldstr "80%" IL_001b: stloc.3 IL_001c: ldstr "80%" IL_0021: stloc.s width IL_0023: ldarg.1 IL_0024: ldc.i4.1 IL_0025: bne.un.s IL_0034 IL_0027: ldstr "60%" IL_002c: stloc.3 IL_002d: ldstr "60%" IL_0032: stloc.s width IL_0034: ldc.i4.5 IL_0035: newarr [mscorlib]System.String IL_003a: stloc.s CS$0$0000 IL_003c: ldloc.s CS$0$0000 IL_003e: ldc.i4.0 IL_003f: ldstr "Height: " IL_0044: stelem.ref IL_0045: ldloc.s CS$0$0000 IL_0047: ldc.i4.1 IL_0048: ldloc.3 IL_0049: stelem.ref IL_004a: ldloc.s CS$0$0000 IL_004c: ldc.i4.2 IL_004d: call string [mscorlib]System.Environment::get_NewLine() IL_0052: stelem.ref IL_0053: ldloc.s CS$0$0000 IL_0055: ldc.i4.3 IL_0056: ldstr "Width: " IL_005b: stelem.ref IL_005c: ldloc.s CS$0$0000 IL_005e: ldc.i4.4 IL_005f: ldloc.s width IL_0061: stelem.ref IL_0062: ldloc.s CS$0$0000 IL_0064: call string [mscorlib]System.String::Concat(string[]) IL_0069: stloc.1 IL_006a: ldloc.2 IL_006b: ldc.i4.1 IL_006c: add IL_006d: stloc.2 IL_006e: ldloc.2 IL_006f: ldc.i4 0xf4240 IL_0074: blt.s IL_0016 IL_0076: ldloc.0 IL_0077: callvirt instance void [System]System.Diagnostics.Stopwatch::Stop() IL_007c: ldstr "testif.txt" IL_0081: ldloc.1 IL_0082: call void [mscorlib]System.IO.File::WriteAllText(string, string) IL_0087: ldloc.0 IL_0088: callvirt instance int64 [System]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() IL_008d: ret } // end of method frmResearch::WithOnlyIf
所以我们可以说, IF-Else块(块#1)比if块(块#2)运行得更快,正如这个论坛的许多人所指出的那样。
检测结果
块1的 10,000,000次迭代
myFlag = 0: 23.8ns per iteration myFlag = 1: 23.8ns per iteration
块2的 10,000,000次迭代
myFlag = 0: 23.8ns per iteration myFlag = 1: 46.8ns per iteration
方块2比方块1慢96%。 有道理,因为Block 2在悲观的情况下做了两倍的工作。
我更喜欢两种情况,这取决于情况。 如果
myFlag
很less有 1,那么它就希望它成为我们必须处理的边缘情况。 如果两者都有相同的可能性,我想要if-else
语法。 但这是偏好,而不是事实。
几十年前,intel80286双通道将会在有条件跳转的情况下停顿,而不会下降到下一条指令。 到了奔腾的时候, CPU预取两个分支path。 但是在我的脑海里,每当我写在else
子句中最常见的代码的时候,我仍然有一种恐惧的冲击。 每当我不得不提醒自己,没有关系了。
Int32 reps = 10000000; private void Block1(int myFlag) { String width; String height; Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < reps; i++) { if (myFlag == 1) { width = String.Format("{0:g}%", 60); height = String.Format("{0:g}%", 60); } else { width = String.Format("{0:g}%", 80); height = String.Format("{0:g}%", 80); } } sw.Stop(); Double time = (Double)sw.Elapsed.Ticks / Stopwatch.Frequency * 1000000000.0 / reps; MessageBox.Show(time.ToString() + " ns"); } private void Block2(int myFlag) { String width; String height; Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < reps; i++) { width = String.Format("{0:g}%", 80); height = String.Format("{0:g}%", 80); if (myFlag == 1) { width = String.Format("{0:g}%", 60); height = String.Format("{0:g}%", 60); } } sw.Stop(); Double time = (Double)sw.Elapsed.Ticks / Stopwatch.Frequency * 1000000000.0 / reps; MessageBox.Show(time.ToString() + " ns"); }
-
String.Format
使IF
96%变慢 -
GetPercentageString(0.60)
使IF
缓慢96%
const reps = 10000000; procedure Block1(myflag: Integer); var width, height: string; i: Integer; t1, t2: Int64; time: Extended; freq: Int64; begin QueryPerformanceCounter(t1); for i := 1 to reps do begin if myFlag = 1 then begin width := '60%'; height := '60%'; end else begin width := '80%'; height := '80%'; end; end; QueryPerformanceCounter(t2); QueryPerformanceFrequency(freq); time := (t2-t1) / freq * 1000000000 / reps; ShowMessage(FloatToStr(time)+ 'ns'); end; procedure Block2(myflag: Integer); var width, height: string; i: Integer; t1, t2: Int64; time: Extended; freq: Int64; begin QueryPerformanceCounter(t1); for i := 1 to reps do begin width := '80%'; height := '80%'; if myFlag = 1 then begin width := '60%'; height := '60%'; end; end; QueryPerformanceCounter(t2); QueryPerformanceFrequency(freq); time := (t2-t1) / freq * 1000000000 / reps; ShowMessage(FloatToStr(time)+ 'ns'); end;
做两倍的工作量需要大约两倍的时间。
答案:IF不如IF-ELSE好。
这里的性能增益可以忽略不计,我称之为微型微型优化。 除非你打算这么做几百万次,否则请去这里阅读。
编辑:(重新:在评论中的问题)
在我看来,第一个更可读。 它以一种现成的格式明确显示了每种情况下的string应该是什么。 第二个是省略了一个案例,所以审查人员需要查看代码的其他部分来确定默认值。 把它放到透视图中,想象在原来的声明/初始化和这个特定的代码块之间的50行代码。 如果在这种情况下变得不清楚,那么会为我决定。
更新
根据Matthew Steeples的回答并根据Lou FrancotestingRelease版本中的代码之后,我发现If-Else的blcoks比if块更好地执行
我在testing应用程序中使用了以下代码块
C#.net代码
块#1
public long WithIfAndElse(int myFlag) { Stopwatch myTimer = new Stopwatch(); string someString = ""; myTimer.Start(); for (int i = 0; i < 1000000; i++) { string height; string width; if (myFlag == 1) { height = "60%"; width = "60%"; } else { height = "80%"; width = "80%"; } someString = "Height: " + height + Environment.NewLine + "Width: " + width; } myTimer.Stop(); File.WriteAllText("testifelse.txt", someString); return myTimer.ElapsedMilliseconds; }
块#2
public long WithOnlyIf(int myFlag) { Stopwatch myTimer = new Stopwatch(); string someString = ""; myTimer.Start(); for (int i = 0; i < 1000000; i++) { string height = "80%"; string width = "80%"; if (myFlag == 1) { height = "60%"; width = "60%"; } someString = "Height: " + height + Environment.NewLine + "Width: " + width; } myTimer.Stop(); File.WriteAllText("testif.txt", someString); return myTimer.ElapsedMilliseconds; }
以下是发布版本的结果
1000000次迭代的结果
块#1
myFlag = 1: 1688 Milliseconds myFlag = 0: 1664 Milliseconds
块#2
myFlag = 1: 1700 Milliseconds myFlag = 0: 1677 Milliseconds
IL代码由ildasm.exe生成
块#1
.method public hidebysig instance int64 WithIfAndElse(int32 myFlag) cil managed { // Code size 144 (0x90) .maxstack 3 .locals init ([0] class [System]System.Diagnostics.Stopwatch myTimer, [1] string someString, [2] int32 i, [3] string height, [4] string width, [5] string[] CS$0$0000) IL_0000: newobj instance void [System]System.Diagnostics.Stopwatch::.ctor() IL_0005: stloc.0 IL_0006: ldstr "" IL_000b: stloc.1 IL_000c: ldloc.0 IL_000d: callvirt instance void [System]System.Diagnostics.Stopwatch::Start() IL_0012: ldc.i4.0 IL_0013: stloc.2 IL_0014: br.s IL_0070 IL_0016: ldarg.1 IL_0017: ldc.i4.1 IL_0018: bne.un.s IL_0029 IL_001a: ldstr "60%" IL_001f: stloc.3 IL_0020: ldstr "60%" IL_0025: stloc.s width IL_0027: br.s IL_0036 IL_0029: ldstr "80%" IL_002e: stloc.3 IL_002f: ldstr "80%" IL_0034: stloc.s width IL_0036: ldc.i4.5 IL_0037: newarr [mscorlib]System.String IL_003c: stloc.s CS$0$0000 IL_003e: ldloc.s CS$0$0000 IL_0040: ldc.i4.0 IL_0041: ldstr "Height: " IL_0046: stelem.ref IL_0047: ldloc.s CS$0$0000 IL_0049: ldc.i4.1 IL_004a: ldloc.3 IL_004b: stelem.ref IL_004c: ldloc.s CS$0$0000 IL_004e: ldc.i4.2 IL_004f: call string [mscorlib]System.Environment::get_NewLine() IL_0054: stelem.ref IL_0055: ldloc.s CS$0$0000 IL_0057: ldc.i4.3 IL_0058: ldstr "Width: " IL_005d: stelem.ref IL_005e: ldloc.s CS$0$0000 IL_0060: ldc.i4.4 IL_0061: ldloc.s width IL_0063: stelem.ref IL_0064: ldloc.s CS$0$0000 IL_0066: call string [mscorlib]System.String::Concat(string[]) IL_006b: stloc.1 IL_006c: ldloc.2 IL_006d: ldc.i4.1 IL_006e: add IL_006f: stloc.2 IL_0070: ldloc.2 IL_0071: ldc.i4 0xf4240 IL_0076: blt.s IL_0016 IL_0078: ldloc.0 IL_0079: callvirt instance void [System]System.Diagnostics.Stopwatch::Stop() IL_007e: ldstr "testifelse.txt" IL_0083: ldloc.1 IL_0084: call void [mscorlib]System.IO.File::WriteAllText(string, string) IL_0089: ldloc.0 IL_008a: callvirt instance int64 [System]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() IL_008f: ret } // end of method frmResearch::WithIfAndElse
块#2
.method public hidebysig instance int64 WithOnlyIf(int32 myFlag) cil managed { // Code size 142 (0x8e) .maxstack 3 .locals init ([0] class [System]System.Diagnostics.Stopwatch myTimer, [1] string someString, [2] int32 i, [3] string height, [4] string width, [5] string[] CS$0$0000) IL_0000: newobj instance void [System]System.Diagnostics.Stopwatch::.ctor() IL_0005: stloc.0 IL_0006: ldstr "" IL_000b: stloc.1 IL_000c: ldloc.0 IL_000d: callvirt instance void [System]System.Diagnostics.Stopwatch::Start() IL_0012: ldc.i4.0 IL_0013: stloc.2 IL_0014: br.s IL_006e IL_0016: ldstr "80%" IL_001b: stloc.3 IL_001c: ldstr "80%" IL_0021: stloc.s width IL_0023: ldarg.1 IL_0024: ldc.i4.1 IL_0025: bne.un.s IL_0034 IL_0027: ldstr "60%" IL_002c: stloc.3 IL_002d: ldstr "60%" IL_0032: stloc.s width IL_0034: ldc.i4.5 IL_0035: newarr [mscorlib]System.String IL_003a: stloc.s CS$0$0000 IL_003c: ldloc.s CS$0$0000 IL_003e: ldc.i4.0 IL_003f: ldstr "Height: " IL_0044: stelem.ref IL_0045: ldloc.s CS$0$0000 IL_0047: ldc.i4.1 IL_0048: ldloc.3 IL_0049: stelem.ref IL_004a: ldloc.s CS$0$0000 IL_004c: ldc.i4.2 IL_004d: call string [mscorlib]System.Environment::get_NewLine() IL_0052: stelem.ref IL_0053: ldloc.s CS$0$0000 IL_0055: ldc.i4.3 IL_0056: ldstr "Width: " IL_005b: stelem.ref IL_005c: ldloc.s CS$0$0000 IL_005e: ldc.i4.4 IL_005f: ldloc.s width IL_0061: stelem.ref IL_0062: ldloc.s CS$0$0000 IL_0064: call string [mscorlib]System.String::Concat(string[]) IL_0069: stloc.1 IL_006a: ldloc.2 IL_006b: ldc.i4.1 IL_006c: add IL_006d: stloc.2 IL_006e: ldloc.2 IL_006f: ldc.i4 0xf4240 IL_0074: blt.s IL_0016 IL_0076: ldloc.0 IL_0077: callvirt instance void [System]System.Diagnostics.Stopwatch::Stop() IL_007c: ldstr "testif.txt" IL_0081: ldloc.1 IL_0082: call void [mscorlib]System.IO.File::WriteAllText(string, string) IL_0087: ldloc.0 IL_0088: callvirt instance int64 [System]System.Diagnostics.Stopwatch::get_ElapsedMilliseconds() IL_008d: ret } // end of method frmResearch::WithOnlyIf
你可以用性能分析器自己回答这个问题,或者只是把它定时(把函数放在你多次调用的循环中)。 对于你所知道的,编译器把它变成相同的代码(你可以检查)
也许你不应该担心这种微观优化。 编写最易读的代码,直到您的工具告诉您要优化的内容。
如前所述,性能在这里不太可能成为问题,如果您对可读性感兴趣,尽pipe您可能想尝试如下的方法:
string height = StdHeight; string width = StdWidth; if (restrictDimensionsFlag) { height = RestrictedHeight; width = RestrictedWidth; }
并将你的标准和限制的大小定义为常量或只读在其他地方(或从configuration读入)。
更正结果。 我做了我自己的testing,我发现这个值我认为更准确。 迭代次数:100,000,000
Flag = 1
标志= 0。
事实上,最坏情况应该是最好情况的两倍是错误的。
使用的代码
string height; string width; int myFlag = 1; Console.WriteLine(" ----------- case 1 ---------------"); DateTime Start = DateTime.Now; for (int Lp = 0; Lp < 100000000; Lp++) { if (myFlag == 1) { height = "60%"; width = "60%"; } else { height = "80%"; width = "80%"; } } TimeSpan Elapsed = DateTime.Now - Start; Console.WriteLine("Time Elapsed: {0} ms",Elapsed.Milliseconds); Console.WriteLine(" ----------- case 2 ---------------"); DateTime Start2 = DateTime.Now; for (int Lp = 0; Lp < 100000000; Lp++) { height = "80%"; width = "80%"; if (myFlag == 1) { height = "60%"; width = "60%"; } } Elapsed = DateTime.Now - Start2; Console.WriteLine("Time Elapsed: {0} ms", Elapsed.Milliseconds);
警告:我从事特定的CPU优化工作已经有一段时间了。
也就是说,如果我要用汇编语言编写这个代码,Block 1每个循环的指令比Block 2less。在汇编/机器代码级别,if / else与if相比基本上是空闲的,因为任何一种情况都会扩展到基本上相同的指令(加载,比较,条件跳转)。
Block1:最好的情况:5,最差的:6
Load value of myFlag Compare to const 1 Jump if zero (equal) :t1 height = "80%"; width = "80%"; Jump :t2 :t1 height = "60%"; width = "60%"; :t2
Block2:最好的情况:6,最差的:7
height = "80%"; width = "80%"; Load value of myFlag Compare to const 1 Jump if non-zero (not-equal) :t1 height = "60%"; width = "60%"; :t1
注意事项:
- 并不是所有的指令都是平等的,特别是在我学习assembly的时候,价格往往更昂贵……现代加工者已经基本消除了这种趋势。
- 现代编译器做了大量的优化工作,可能会将代码的结构从其中一个结构改变为另一个结构,或者完全改变完全不同的方法。 (我已经看到了一些相当有创意的数组索引,可以用在这种情况下)
结论:总的来说,即使在机器码级别上的差异在这两个stream程之间也是非常小的。 select一个最适合你的方法,让编译器找出最佳方法来优化它。 像这样的所有相对较小的情况应该或多或less地被这样处理。 例如改变计算次数或者减less昂贵的函数调用的macros优化应该被积极地实现。 这样的小循环优化在实践中不太可能产生真正的差异,特别是在编译器完成之后。
更快的方法是将高度和宽度视为ints / float,并在最后一秒将它们转换为string。假设您正在频繁地执行此操作,以至于无法处理(提示:您不是)。
看着IL我认为你有一个更大的问题,而不是如果声明更快。 因为你的方法没有副作用,所以编译器实际上是在debugging模式下完全移除了if语句的内容,并且完全以释放模式移除了if语句。
打开类似ILSpy的.exe文件将validation这一点。
在找出这个问题的答案之前,你必须重新开始一些需要知道和持续时间的事情。
我会使用块#2 。 正如你所说的那样,性能受到的影响可以忽略不计,但它绝对更短,更容易阅读。 实质上,除非满足特定的条件,否则您将为您的variables设置默认值。
块#2更具可读性。 但是,贵公司是否有编码标准? 如果是这样,我会尽可能地追随他们或build议一致的改进。
根据块#1中的性能,高度和宽度用空值进行初始化,但是随后被分配(不pipe条件如何)。 性能差异接近零。
还有,你用ILDASM检查IL吗?
我通常使用Block#2方法,只是因为我知道有问题的variables最初被设置为默认值