2013-04-16 7 views
7

Mi sono imbattuto in questo mentre facevo qualche benchmarking.La fusione della struttura dell'oggetto per il confronto nullo non sta causando il pugilato?

bool b; 
MyStruct s; 
for (int i = 0; i < 10000000; i++) 
{ 
    b = (object)s == null; 
} 

Debug: 200 ms

Stampa: 5 ms

bool b; 
MyStruct? s = null; 
for (int i = 0; i < 10000000; i++) 
{ 
    b = (object)s == null; 
} 

debug: 800 ms

di uscita: 800 ms

Riesco a capire questo risultato poiché il cast della struttura nullable su object mi fornisce un tipo in scatola di tale struttura. Ma perché non eseguire il casting da struct s a object per eseguire il confronto nullo (come nel primo metodo), si ottiene la stessa prestazione? È che il compilatore sta ottimizzando la chiamata per restituire false sempre come struct non può essere nullo?

+0

questo codice non viene compilato; Errore 1 Uso della variabile locale non assegnata 's' per il secondo ciclo – Fredou

+0

@Fredou Esatto. Un errore di battitura, infatti. Voglio aggiornare la mia risposta con un po 'di benchmarking - ho trovato un errore nei miei tempi – nawfal

+0

Questo codice, compilato, sembra di essere in esecuzione ciclo vuoto, si dovrebbe pubblicare il tuo codice di riferimento reale, perché non vedo come si può ottenere 7500ms – Fredou

risposta

8

Sì, il compilatore lo sta ottimizzando.

Sa che una struttura non può mai essere nullo, quindi il risultato del suo casting su un oggetto non può mai essere nullo, quindi nel primo campione sarà impostato su b su falso. Infatti, se si utilizza Resharper, verrà visualizzato un avviso che l'espressione è sempre falsa.

Per il secondo, naturalmente, un valore nullo può essere nullo, quindi è necessario effettuare il controllo.

(È inoltre possibile utilizzare Reflector per ispezionare il codice IL generato dal compilatore per verificare questo.)

Il codice di prova originale non è buono perché il compilatore sa che lo struct annullabile sarà sempre nullo e sarà quindi anche ottimizzare via quel ciclo. Non solo, ma in una versione build il compilatore si rende conto che b non viene utilizzato e ottimizza l'intero ciclo.

Per evitare questo, e per mostrare ciò che sarebbe accaduto nel codice più realistico, provare in questo modo:

using System; 
using System.Diagnostics; 

namespace ConsoleApplication1 
{ 
    internal class Program 
    { 
     private static void Main(string[] args) 
     { 
      bool b = true; 
      MyStruct? s1 = getNullableStruct(); 
      Stopwatch sw = Stopwatch.StartNew(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       b &= (object)s1 == null; // Note: Redundant cast to object. 
      } 

      Console.WriteLine(sw.Elapsed); 

      MyStruct s2 = getStruct(); 
      sw.Restart(); 

      for (int i = 0; i < 10000000; i++) 
      { 
       b &= (object)s2 == null; 
      } 

      Console.WriteLine(sw.Elapsed); 
     } 

     private static MyStruct? getNullableStruct() 
     { 
      return null; 
     } 

     private static MyStruct getStruct() 
     { 
      return new MyStruct(); 
     } 
    } 

    public struct MyStruct {} 
} 
+1

in questo codice, sia ad anello avrà corpo vuoto – Fredou

+1

@Fredou Ho aggiunto un po 'di codice di test esplicito per evitare questo problema, quindi questo dimostra in modo corretto. –

+1

infatti per replicare il comportamento di tutto ciò che serve è '' & = nel codice originale, non c'è bisogno di creare il metodo che restituisce la struct – Fredou

3

infatti sia ad anello avrà un corpo vuoto quando compilato!

a fare la seconda si comportano ciclo, si dovrà rimuovere il (object) scacciando

questo è quello che sembra quando compilo il codice,

public struct MyStruct 
{ 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     test1(); 
     test2(); 
    } 

    public static void test1() 
    { 
     Stopwatch sw = new Stopwatch(); 
     bool b; 
     MyStruct s; 
     for (int i = 0; i < 100000000; i++) 
     { 
      b = (object)s == null; 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 

    public static void test2() 
    { 
     Stopwatch sw = new Stopwatch(); 
     bool b; 
     MyStruct? s = null; 
     for (int i = 0; i < 100000000; i++) 
     { 
      b = (object)s == null; 
     } 
     sw.Stop(); 
     Console.WriteLine(sw.ElapsedMilliseconds); 
     Console.ReadLine(); 
    } 
} 

IL:

il MyStruct (vuoto poiché non ne hai fornito nessuno)

.class public sequential ansi sealed beforefieldinit ConsoleApplication1.MyStruct 
extends [mscorlib]System.ValueType 
{ 
    .pack 0 
    .size 1 

} // end of class ConsoleApplication1.MyStruct 

gli abeti t ciclo nel tuo esempio

.method public hidebysig static 
void test1() cil managed 
{ 
// Method begins at RVA 0x2054 
// Code size 17 (0x11) 
.maxstack 2 
.locals init (
    [0] valuetype ConsoleApplication1.MyStruct s, 
    [1] int32 i 
) 

IL_0000: ldc.i4.0 
IL_0001: stloc.1 
IL_0002: br.s IL_0008 
// loop start (head: IL_0008) 
    IL_0004: ldloc.1 
    IL_0005: ldc.i4.1 
    IL_0006: add 
    IL_0007: stloc.1 

    IL_0008: ldloc.1 
    IL_0009: ldc.i4 100000000 
    IL_000e: blt.s IL_0004 
// end loop 

IL_0010: ret 
} // end of method Program::test1 

il secondo ciclo

.method public hidebysig static 
void test2() cil managed 
{ 
// Method begins at RVA 0x2074 
// Code size 25 (0x19) 
.maxstack 2 
.locals init (
    [0] valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct> s, 
    [1] int32 i 
) 

IL_0000: ldloca.s s 
IL_0002: initobj valuetype [mscorlib]System.Nullable`1<valuetype ConsoleApplication1.MyStruct> 
IL_0008: ldc.i4.0 
IL_0009: stloc.1 
IL_000a: br.s IL_0010 
// loop start (head: IL_0010) 
    IL_000c: ldloc.1 
    IL_000d: ldc.i4.1 
    IL_000e: add 
    IL_000f: stloc.1 

    IL_0010: ldloc.1 
    IL_0011: ldc.i4 100000000 
    IL_0016: blt.s IL_000c 
// end loop 

IL_0018: ret 
} // end of method Program::test2 
Problemi correlati