如果为null,则实例化变量

Cod*_*313 21 .net c#

if (x == null) x = new X();
Run Code Online (Sandbox Code Playgroud)

x = x ?? new X();
Run Code Online (Sandbox Code Playgroud)

这两个中哪一个实际上更高效?一旦编译完成,它们会有效地结束(结果会x = x;是NO-OP)吗?

Mar*_*ers 31

查看中间语言代码有一个区别:

.method private hidebysig instance void Method1() cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldfld class X Program::x
    L_0006: brtrue.s L_0013
    L_0008: ldarg.0 
    L_0009: newobj instance void X::.ctor()
    L_000e: stfld class X Program::x
    L_0013: ret 
}

.method private hidebysig instance void Method2() cil managed
{
    .maxstack 8
    L_0000: ldarg.0 
    L_0001: ldarg.0 
    L_0002: ldfld class X Program::x
    L_0007: dup 
    L_0008: brtrue.s L_0010
    L_000a: pop 
    L_000b: newobj instance void X::.ctor()
    L_0010: stfld class X Program::x
    L_0015: ret 
}
Run Code Online (Sandbox Code Playgroud)

这是我为了得到这个而编译的代码:

void Method1()
{
    if (x == null) x = new X();
}

void Method2()
{
    x = x ?? new X();
}
Run Code Online (Sandbox Code Playgroud)

为了确定哪个更快,你应该两个时间.

Method     Initial condition   Iterations per second
---------------------------------------------------
NullCheck  x is null           33 million  
Coalesce   x is null           33 million
NullCheck  x is not null       40 million
Coalesce   x is not null       33 million

结论:

  • 在这种情况下,它们大致相同,其中值最初为null.
  • 当x已经不为空时,使用if语句的方法比空合并运算符快得多.

当x不为null时的差异看起来可能是由于空合并运算符将x的值分配回x(stfld在IL中),而stfld当x不为空时,null检查跳过指令.

两者都是如此之快,你必须有一个非常紧密的循环来注意到差异.如果您已使用数据分析代码,则应该只进行这些性能优化.不同的情况,不同版本的.NET,不同的编译器等可能会产生不同的结果.

如果有人想知道我如何得到这些结果或重现它们,这里是我使用的代码:

using System;

class X { }

class Program
{
    private X x;

    private X xNull = null;
    private X xNotNull = new X();

    private void Method1Null()
    {
        x = xNull;
        if (x == null) x = xNotNull;
    }

    private void Method2Null()
    {
        x = xNull;
        x = x ?? xNotNull;
    }

    private void Method1NotNull()
    {
        x = xNotNull;
        if (x == null) x = xNotNull;
    }

    private void Method2NotNull()
    {
        x = xNotNull;
        x = x ?? xNotNull;
    }

    private const int repetitions = 1000000000;

    private void Time(Action action)
    {
        DateTime start = DateTime.UtcNow;
        for (int i = 0; i < repetitions; ++i)
        {
            action();
        }
        DateTime end = DateTime.UtcNow;
        Console.WriteLine(repetitions / (end - start).TotalSeconds);
    }

    private void Run()
    {
        Time(() => { Method1Null(); });
        Time(() => { Method2Null(); });
        Time(() => { Method1NotNull(); });
        Time(() => { Method2NotNull(); });
        Console.WriteLine("Finished");
        Console.ReadLine();
    }

    private static void Main()
    {
        new Program().Run();
    }
}
Run Code Online (Sandbox Code Playgroud)

免责声明:没有基准是完美的,这个bechmark 远非完美,主要是为了保持简单.我已经运行了许多不同的测试,例如使用不同顺序的方法,首先是有没有"预热",有不同的时间长度等等.我每次都得到大致相同的结果.我没有任何证据可以证明这种或那种方式,所以任何有利于一种方法或另一种方法的偏见都是偶然的.

  • 底线 - 如果您在1秒的时间间隔内在代码中调用了这个超过2000万次,那么您会发现存在差异.否则,它不会产生任何性能差异. (10认同)
  • 不同的IL并不一定意味着JIT将它们转换为两个不同的机器代码. (5认同)
  • 太棒了:)虽然您应该考虑在Time方法中使用Stopwatch类:http://msdn.microsoft.com/en-us/library/system.diagnostics.stopwatch.aspx (2认同)

Dan*_*ite 16

我不担心这种过早的优化.我确信C#编译器的设计者足够聪明,可以为你做到这一点.


Ond*_*cny 6

没有interresting性能差异可言.最重要的是,if (x == null) x = new X();更具有可读性.

回答你原来的问题:一个聪明的优化编译器将以与编译x = x ?? new X()相同的方式进行编译if (x == null) x = new X();.

因此,在编译器上留下微优化,并专注于代码可读性和清晰度.


更新:要了解有关优化实践的更多信息,请阅读维基百科上的这篇文章,然后就Google问题的性质而言,谷歌"过早优化是所有邪恶的根源".

  • 这有点主观 - 应该"对我来说更具可读性" (10认同)
  • 我完全同意; 你应该设计可维护性,因为性能差异为零. (2认同)