if (x == null) x = new X();
против
x = x ?? new X();
какой из этих двух фактически более эффективен? как только они скомпилированы, они фактически завершатся как одно и то же (если бы x = x;
был NO-OP в результате)?
if (x == null) x = new X();
против
x = x ?? new X();
какой из этих двух фактически более эффективен? как только они скомпилированы, они фактически завершатся как одно и то же (если бы x = x;
был NO-OP в результате)?
Глядя на код промежуточного языка, есть разница:
.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
Вывод:
Разница, когда 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 далек от совершенства, в основном, чтобы все было просто. Я провела множество различных тестов, например. с методами в другом порядке, с и без "разогрева" сначала, в разные промежутки времени и т.д. Я получаю примерно одинаковые результаты каждый раз. У меня не было ничего, чтобы доказать, так или иначе, поэтому любое смещение в пользу одного метода или другого случая является случайным.
Я бы не стал беспокоиться об этой преждевременной оптимизации. Я уверен, что разработчики компилятора С# были достаточно умны, чтобы сделать это для вас.
Отличия в производительности не наблюдается. Самое главное, что if (x == null) x = new X();
гораздо читабельнее.
Чтобы ответить на ваш первоначальный вопрос: умный оптимизирующий компилятор будет компилировать x = x ?? new X()
так же, как if (x == null) x = new X();
.
Следовательно, оставляйте микро-оптимизацию в компиляторе и концентрируйтесь на читаемости и ясности кода.
Обновление. Чтобы узнать больше об оптимизации, прочитайте эту статью о википедии, а затем, в отношении характера вашего вопроса, Google для "преждевременная оптимизация - корень всех злых" .
Как отмечали другие, разница в производительности между этими двумя не отличается от других, однако между ними существует важное различие, хотя это не так очевидно из вашего примера.
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.