2011-02-06 10 views
17
if (x == null) x = new X(); 

frenteCrear instancias de una variable nula si

x = x ?? new X(); 

cuál de estos dos es en realidad más performante? una vez compilados, terminan efectivamente como lo mismo (¿sería x = x; NO-OP como resultado)?

+0

Yo diría que el segundo sería más rápido porque el ?? El operador tiene un propósito específico, pero puede compilar esos dos fragmentos y comparar el IL generado y/o ejecutar una prueba para ver cuál es más rápido. – tenor

+0

@ MasterMax1313 la diferencia de IL no es tan irrelevante que hayas votado negativamente ... todavía he actualizado mi respuesta –

+0

Ten en cuenta que ninguno de los dos es seguro para subprocesos. Y uno generalmente espera que getters sea seguro para subprocesos. Recomiendo usar 'Lazy ' si * realmente * necesita una inicialización lenta. Es un poco más lento pero evita varias dificultades. – CodesInChaos

Respuesta

26

Mirando el código de lenguaje intermedio hay una diferencia:

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

Aquí está el código compilé para conseguir esto:

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

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

para estar seguro de que es más rápido que si el tiempo de ambos.

 
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 

Conclusión:

  • Son casi lo mismo en este caso donde el valor es inicialmente nulo.
  • El método que utiliza una instrucción if es considerablemente más rápido que el operador nulo coalescente cuando x ya no es nulo.

La diferencia cuando x no es miradas nulos como que podría ser debido a que el operador coalescente nula asignar el valor de x de nuevo a x (stfld en la IL), mientras que el cheque nulo salta por encima de la instrucción stfld cuando x es no nulo.

Ambos son tan rápidos que debe tener un muy lazo cerrado para notar la diferencia. Solo debe hacer este tipo de optimizaciones de rendimiento si ha perfilado su código con sus datos. Diferentes situaciones, diferentes versiones de .NET, diferentes compiladores, etc. pueden producir resultados diferentes.

En caso de que alguien quiere saber cómo llegué estos resultados o reproducirlas, aquí está el código que he utilizado:

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(); 
    } 
} 

de responsabilidad: No de referencia es perfecto, y esto bechmark es ahora de ser perfecto, sobre todo para mantener las cosas simples. He realizado numerosas pruebas diferentes, p. con los métodos en un orden diferente, con y sin "calentamiento" primero, en diferentes períodos de tiempo, etc. Obtengo aproximadamente los mismos resultados cada vez. No tenía nada que demostrar de una forma u otra, así que cualquier parcialidad que favorezca un método u otro es accidental.

+1

+1 Gracias por proporcionar el IL. –

+0

+1 para una respuesta rápida ... en el momento en que estaba ejecutando el código –

+5

Distinto IL no significa necesariamente que el JIT los traduce en dos partes diferentes de código de máquina. –

16

No me preocuparía esta optimización prematura. Estoy seguro de que los diseñadores del compilador de C# fueron lo suficientemente inteligentes como para hacer esto por ti.

6

No hay ninguna diferencia interresting rendimiento en absoluto. Lo más importante es que if (x == null) x = new X(); es mucho más legible.

Para responder a su pregunta original: un compilador inteligente y optimizador compilaría x = x ?? new X() de la misma manera que if (x == null) x = new X();.

Por lo tanto, deje micro-optimizaciones en el compilador y concéntrese en la legibilidad y claridad del código.


Actualización: Para obtener más información sobre las prácticas de optimización leer this article on wikipedia y luego, en lo que respecta a la naturaleza de su pregunta, Google para “premature optimization is the root of all evil”.

+10

Eso es un poco subjetivo - Debería ser "es mucho más legible para mí " – PostMan

+2

Estoy completamente de acuerdo; debe diseñar para la mantenibilidad dado que la diferencia de rendimiento es nula. –

+1

@PostMan Desde mi punto de vista, y apostaría que la mayoría de la gente lo siente igual, cualquier construcción similar a 'x = x' huele a efectos secundarios y hace que el lector tenga que pensar mucho más en contraste con el simple 'si'. –

4

Como han mencionado otros, la diferencia de rendimiento entre los dos no es muy diferente, sin embargo, hay una diferencia importante entre los dos, aunque no es tan evidente en su ejemplo.

if (x == null) x = new X(); crea una condición de carrera potencial donde x se puede actualizar por otro hilo entre la comprobación de nulo y la creación, perdiendo así un objeto.

x = x ?? new X(); crea primero una copia de la variable, por lo tanto, no existe una condición de carrera potencial.

Esto es más un problema en situaciones donde se hace referencia al objeto. Por ejemplo:

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. 
+1

Esto no * previene * las condiciones de carrera en general (por ejemplo, no debe tomarse como cualquier forma de "protección de subprocesamiento" implícita sin un contexto muy refinado y analizado) - simplemente garantiza que este último caso siempre se invoca en un objeto no nulo (cualquiera que sea). Volvería a votar, pero estoy fuera hoy :( –

+0

-1 tu razonamiento es incorrecto - 1) mencionar una variable solo una vez en una expresión no significa que la ubicación de la memoria tendrá acceso solo una vez durante la evaluación de esa expresión, 2) seguridad de hilos/atomicidad de lectura/escritura variable (prevención de condición de carrera) no tiene nada que ver con este caso. –

+0

@Ondrej Tucny - No dije nada acerca de mencionar una variable una vez. Me refería a la IL publicada por otros, donde la versión coalescente hace una copia de la variable antes de ser utilizada, mientras que la versión if no lo hace. y tampoco he dicho nada sobre la seguridad de las hebras o la atomicidad. Me refería solo al hecho de que es posible hacer una excepción de referencia nula con la versión if, mientras que la ?? versión que no. Si el estado es correcto o no es una historia diferente. Te votaría por hacer suposiciones, especialmente aquellas que no son válidas ... si pudiera. –

Cuestiones relacionadas