【问题标题】:Does Optional Else affect performance?Optional Else 会影响性能吗?
【发布时间】:2012-12-02 08:55:18
【问题描述】:

int i = 3;

这段代码有没有性能上的区别:

if(i == 2)
   DoA();
if(i == 3)
   DoB();
if(i == 4)
   DoC();

还有这段代码:

if(i == 2)
   DoA();
else if(i == 3)
   DoB();
else if(i == 4)
   DoC();

我想知道使用可选的 ELSE 是否会影响 CPU 理解代码的方式。我一直认为当我们使用第二种方法时,如果i2 那么CPU 不会检查其他两个条件,但是在第一种方法中虽然第一个条件为真(i == 2)但CPU 确实会检查第二和第三个条件。这是真的吗?

【问题讨论】:

  • 确实如此。不过,请不要遭受过早的优化。做有意义的事,而不是更快的事。
  • @Mahdi jan,代码优化可能会为你解决这个问题,我认为上面的两个代码是相同的。
  • @JanDvorak 谢谢,我只是很好奇,我永远不会更改代码,只是想知道 BlackBox 中发生了什么。
  • 更重要的是,在第二个案例中,您告诉我们读者,您希望这些案例是互斥的。在第一种情况下,我们必须检查DoA() 是否可能更改i
  • @BoPersson 这也是一个非常聪明的观点,谢谢。但就我而言,i 是稳定的。

标签: c# if-statement cpu cil


【解决方案1】:

您不应该根据性能来决定这一点。 两个版本的代码含义不同。您应该使用正确的那个。

例如:

if (s == null)
{
    // do something
    s = GetNewValue();
}
if (s == "")
{
    // do something else
}

没有else,这段代码意味着:

  • 如果snull,则执行第一个块
  • 然后,如果该块将 s 更改为 ""(或 s 开头是 ""),则执行第二个块。

在第二个if之前多了一个else,代码的意思是:

  • 如果snull,则执行第一个块。
  • 如果s不是以null开头的并且等于"",则执行第二个块。

如果由于块不修改您要比较的变量而没有区别,使用else,因为它使您的代码含义明确。

【讨论】:

  • 我明白你的意思,但我在想象i 不会改变并且条件总是不同的情况,那就是使用/不使用 ELSE 很重要。谢谢。
  • @mahditahsildari:这就是我的最后一句话。
【解决方案2】:

当然会影响——更好!这与不必要的完全相反,因为通过使用这些 else,编译器将跳过其他 if-checks 并获得更好的性能。

但是您不必为此担心,您的示例中的性能差异是如此之小以至于微不足道。

【讨论】:

  • 感谢@JumenTim 注意到标题错误 :) 我编辑了标题
【解决方案3】:

这是您的两个语句的 IL 代码:

.method private hidebysig static void m1() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.3 
    L_0002: stloc.0 
    L_0003: ldloc.0 
    L_0004: ldc.i4.2 
    L_0005: ceq 
    L_0007: ldc.i4.0 
    L_0008: ceq 
    L_000a: stloc.1 
    L_000b: ldloc.1 
    L_000c: brtrue.s L_0014
    L_000e: call void ConsoleApplication1.Program::DoA()
    L_0013: nop 
    L_0014: ldloc.0 
    L_0015: ldc.i4.3 
    L_0016: ceq 
    L_0018: ldc.i4.0 
    L_0019: ceq 
    L_001b: stloc.1 
    L_001c: ldloc.1 
    L_001d: brtrue.s L_0025
    L_001f: call void ConsoleApplication1.Program::DoB()
    L_0024: nop 
    L_0025: ldloc.0 
    L_0026: ldc.i4.4 
    L_0027: ceq 
    L_0029: ldc.i4.0 
    L_002a: ceq 
    L_002c: stloc.1 
    L_002d: ldloc.1 
    L_002e: brtrue.s L_0036
    L_0030: call void ConsoleApplication1.Program::DoC()
    L_0035: nop 
    L_0036: ret 
}

.method private hidebysig static void m2() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.3 
    L_0002: stloc.0 
    L_0003: ldloc.0 
    L_0004: ldc.i4.2 
    L_0005: ceq 
    L_0007: ldc.i4.0 
    L_0008: ceq 
    L_000a: stloc.1 
    L_000b: ldloc.1 
    L_000c: brtrue.s L_0016
    L_000e: call void ConsoleApplication1.Program::DoA()
    L_0013: nop 
    L_0014: br.s L_003a
    L_0016: ldloc.0 
    L_0017: ldc.i4.3 
    L_0018: ceq 
    L_001a: ldc.i4.0 
    L_001b: ceq 
    L_001d: stloc.1 
    L_001e: ldloc.1 
    L_001f: brtrue.s L_0029
    L_0021: call void ConsoleApplication1.Program::DoB()
    L_0026: nop 
    L_0027: br.s L_003a
    L_0029: ldloc.0 
    L_002a: ldc.i4.4 
    L_002b: ceq 
    L_002d: ldc.i4.0 
    L_002e: ceq 
    L_0030: stloc.1 
    L_0031: ldloc.1 
    L_0032: brtrue.s L_003a
    L_0034: call void ConsoleApplication1.Program::DoC()
    L_0039: nop 
    L_003a: ret 
}

看起来生成的 IL(s)相同。

编辑 1

在方法m2():

L_0014: br.s L_003a
.
.
.
L_0027: br.s L_003a

那么方法 'm2()' 更快。

【讨论】:

  • 这是否意味着编译器选择了冗余检查?这有点令人惊讶。
  • 其实,没有。它们是不相同的。后者包括在每个 if 块之后的无条件跳转。
  • @Jan:是的,代码优化在代码上做了更多的工作。有时IL代码与原始代码有很多不同的逻辑。
  • @JanDvorak 我同意,如果它们是相同的,那么跳转在那里做什么?他们是不同的
  • “方法 'm2()' 更快” 你实际测量过吗?如果不是,那么我不清楚它实际上会更快,因为 JIT 编译器可以消除冗余检查。
【解决方案4】:

是的,使用 else if,考虑以下代码:

if(predicateA){
  //do Stuff
}
if(predicateB){
  // do more stuff
}

if(predicateA){
  //do stuff 
}
else if(predicateB){
  //do stuff
}

在第二种情况下,如果 predicateA 为真,则不需要计算 predicateB(和任何其他谓词)(因此整个代码将执行得更快),而在第一个示例中,如果 predicateA 为真,则 predicateB 仍将始终被评估,如果 predicateA 和 predicateB 不互斥,你也可能会得到一些意想不到的惊喜。

【讨论】:

  • 如果它们互斥的,就像问题中一样,编译器会意识到这一点并生成相同的代码。
【解决方案5】:

如果“DoX”是像“a = b”这样的语句;那么第一个执行速度可能更快,并且编译器仍然可能无法找出互斥比较的最佳选项。

在某些架构中,这三个比较可以转换为:

 cmp a,2;
 movlt  b, c;
 moveq  c, d;
 movgt  e, f;

如果这些是非常简单的语句,程序员会有所帮助。

为了获得更好的性能,转换问题的其他一些实用方法是使用函数指针,并确保每一步都不会访问超出范围的数组。

 void (*do)()[]={ doA, doB, doC };

 do[i]();  
 // mov eax, do[eax*4];
 // call [eax]

【讨论】:

    猜你喜欢
    • 2020-06-22
    • 1970-01-01
    • 2021-04-29
    • 2012-02-04
    • 2011-01-28
    • 1970-01-01
    • 2010-09-26
    • 2012-09-29
    • 1970-01-01
    相关资源
    最近更新 更多