Подтвердить что ты не робот

Создание экземпляра переменной, если значение null

if (x == null) x = new X();

против

x = x ?? new X();

какой из этих двух фактически более эффективен? как только они скомпилированы, они фактически завершатся как одно и то же (если бы x = x; был NO-OP в результате)?

4b9b3361

Ответ 1

Глядя на код промежуточного языка, есть разница:

.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 
}

Вот код, который я скомпилировал, чтобы получить следующее:

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

void Method2()
{
    x = x ?? new X();
}

Чтобы быть уверенным, что это быстрее, вам нужно время обойти.

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.
  • Метод, использующий оператор if, значительно быстрее, чем оператор нулевой коалесценции, когда x уже не null.

Разница, когда x не равна null, выглядит так, как это может быть из-за оператора нулевой коалесценции, присваивающего значение x обратно x (stfld в IL), тогда как нулевая проверка перескакивает по команде stfld, когда x не является нулевым.

Оба настолько быстрей, что у вас должен быть очень узкий цикл, чтобы заметить разницу. Вы должны делать только такие оптимизации производительности, если вы профилировали свой код своими данными. Различные ситуации, разные версии .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();
    }
}

Отказ от ответственности: нет идеального теста, и этот bechmark далек от совершенства, в основном, чтобы все было просто. Я провела множество различных тестов, например. с методами в другом порядке, с и без "разогрева" сначала, в разные промежутки времени и т.д. Я получаю примерно одинаковые результаты каждый раз. У меня не было ничего, чтобы доказать, так или иначе, поэтому любое смещение в пользу одного метода или другого случая является случайным.

Ответ 2

Я бы не стал беспокоиться об этой преждевременной оптимизации. Я уверен, что разработчики компилятора С# были достаточно умны, чтобы сделать это для вас.

Ответ 3

Отличия в производительности не наблюдается. Самое главное, что if (x == null) x = new X(); гораздо читабельнее.

Чтобы ответить на ваш первоначальный вопрос: умный оптимизирующий компилятор будет компилировать x = x ?? new X() так же, как if (x == null) x = new X();.

Следовательно, оставляйте микро-оптимизацию в компиляторе и концентрируйтесь на читаемости и ясности кода.


Обновление. Чтобы узнать больше об оптимизации, прочитайте эту статью о википедии, а затем, в отношении характера вашего вопроса, Google для "преждевременная оптимизация - корень всех злых" .

Ответ 4

Как отмечали другие, разница в производительности между этими двумя не отличается от других, однако между ними существует важное различие, хотя это не так очевидно из вашего примера.

if (x == null) x = new X(); создает потенциальное состояние гонки, где x может быть обновлен другим потоком между проверкой на null и создание, тем самым теряя объект.

x = x ?? new X(); сначала создает копию переменной, поэтому нет потенциального состояния гонки.

Это больше проблема в ситуациях, когда вы ссылаетесь на объект. Например:

if (x != null) x.DoSomething();  // this can cause a null reference exception
                                 // if another thread nulls x between the check
                                 // and the call to DoSomething()

(x = x ?? new X()).DoSomething()     // this should be fine.