2012-07-13 14 views
7

Ho notato che i tempi di avvio variano a seconda di qui che ho inserito un pezzo di codice di inizializzazione. Ho pensato che fosse davvero strano, così ho scritto un piccolo benchmark, che ha confermato i miei sospetti. Sembra che il codice che viene eseguito prima che venga invocato il metodo principale è più lento del normale.Il codice nel costruttore statico funziona più lentamente

Perché lo Benchmark(); funziona a velocità diverse a seconda che venga chiamato prima e dopo il percorso regolare del codice?

Ecco il codice di riferimento:

class Program { 
    static Stopwatch stopwatch = new Stopwatch(); 
    static Program program = new Program(); 

    static void Main() { 
     Console.WriteLine("main method:"); 
     Benchmark(); 
     Console.WriteLine(); 

     new Program(); 
    } 

    static Program() { 
     Console.WriteLine("static constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    public Program() { 
     Console.WriteLine("public constructor:"); 
     Benchmark(); 
     Console.WriteLine(); 
    } 

    static void Benchmark() { 
     for (int t = 0; t < 5; t++) { 
      stopwatch.Reset(); 
      stopwatch.Start(); 
      for (int i = 0; i < 1000000; i++) 
       IsPrime(2 * i + 1); 
      stopwatch.Stop(); 
      Console.WriteLine(stopwatch.ElapsedMilliseconds + " ms"); 
     } 
    } 

    static Boolean IsPrime(int x) { 
     if ((x & 1) == 0) 
      return x == 2; 
     if (x < 2) 
      return false; 
     for (int i = 3, s = (int)Math.Sqrt(x); i <= s; i += 2) 
      if (x % i == 0) 
       return false; 
     return true; 
    } 
} 

I risultati mostrano che Benchmark() corre quasi il doppio più lento sia per il costruttore statico e di costruzione per la static Program program immobile:

// static Program program = new Program() 
public constructor: 
894 ms 
895 ms 
887 ms 
884 ms 
883 ms 

static constructor: 
880 ms 
872 ms 
876 ms 
876 ms 
872 ms 

main method: 
426 ms 
428 ms 
426 ms 
426 ms 
426 ms 

// new Program() in Main() 
public constructor: 
426 ms 
427 ms 
426 ms 
426 ms 
426 ms 

Raddoppiare il numero di iterazioni in il ciclo di riferimento fa raddoppiare tutte le volte, suggerendo che la penalizzazione delle prestazioni sostenuta non è costante, ma un fattore.

// static Program program = new Program() 
public constructor: 
2039 ms 
2024 ms 
2020 ms 
2019 ms 
2013 ms 

static constructor: 
2019 ms 
2028 ms 
2019 ms 
2021 ms 
2020 ms 

main method: 
1120 ms 
1120 ms 
1119 ms 
1120 ms 
1120 ms 

// new Program() in Main() 
public constructor: 
1120 ms 
1128 ms 
1124 ms 
1120 ms 
1122 ms 

Perché dovrebbe essere così? Avrebbe senso se l'inizializzazione sarebbe altrettanto veloce se fosse eseguita dove appartiene. Il test è stato eseguito in .NET 4, modalità di rilascio, ottimizzazioni in.

+2

Qual è la domanda esattamente? – jcolebrand

+0

Impostazioni di compilazione? Versione del framework? – user7116

+0

Controlla se le mie modifiche sono corrette (non ho idea del perché), l'ho provato su .Net 4/release con risultati simili. –

risposta

3

Questo è un problema molto interessante. Ho passato un po 'di tempo a sperimentare varianti del tuo programma. Qui ci sono alcune osservazioni:

  1. Se si sposta il Benchmark() metodo statico in una classe diversa, la pena di prestazioni per il costruttore statico scompare.

  2. Se si effettua il metodo Benchmark() in un metodo di istanza, la penalità delle prestazioni scompare.

  3. Quando analizzo i casi veloci (1, 2) e quelli lenti (3, 4), i casi lenti impiegano i tempi supplementari nei metodi di supporto CLR, in particolare JIT_GetSharedNonGCStaticBase_Helper.

Sulla base di queste informazioni, posso ipotizzare su cosa sta succedendo. Il CLR deve garantire che ogni costruttore statico venga eseguito al massimo una volta. Una complicazione è che i costruttori statici possono formare un ciclo (ad es., Se la classe A contiene un campo statico di tipo B e la classe B contiene un campo statico di tipo A).

Quando si esegue all'interno di un costruttore statico, l'inserto del compilatore JIT verifica alcune chiamate al metodo statico per impedire potenziali cicli infiniti dovuti alle dipendenze della classe ciclica. Una volta che il metodo statico viene chiamato dall'esterno di un costruttore statico, il CLR ricompila il metodo per rimuovere i controlli.

Questo dovrebbe essere abbastanza vicino a quello che sta succedendo.

+0

Grazie per aver guardato di più al problema, in particolare alla profilazione. Non sono sicuro della tua analisi; implicherebbe un sovraccarico costante nei miei test. Ho aggiornato la mia domanda per dimostrare che la penalizzazione delle prestazioni sembra essere un fattore. – Zong

+0

C'è un overhead extra ** per ogni chiamata IsPrime() ** perché ogni chiamata IsPrime deve essere circondata da assegni. Ad esempio, se rendi IsPrime più costoso (ad es. Chiama IsPrime (1000000000 + 2 * i)), la differenza tra i quattro casi svanisce. –

+0

I.e, se raddoppierete il numero di iterazioni, raddoppierete anche il numero di chiamate IsPrime e raddoppierete di conseguenza il numero di controlli eseguiti. –

3

Questo è un fatto molto ben documentato.

I costruttori statici sono lenti. Il runtime .net non è abbastanza intelligente per ottimizzarli.

riferiscono: Performance penalty of static constructors

costruttori statici espliciti sono costosi perché richiedono che il runtime per assicurare che il valore è impostato esattamente prima di ogni membro della classe si accede. Il costo esatto è dipende dallo scenario, ma in alcuni casi può essere abbastanza evidente.

+0

Non penso che si applichi l'articolo collegato. L'articolo dice che un tipo con un costruttore statico è contrassegnato prima diFieldInit, e di conseguenza il lavoro con quel tipo comporta costi extra di runtime. Tuttavia, OP non sta confrontando due tipi, uno con un costruttore statico e uno senza. Esiste un solo tipo nell'intero benchmark e quel tipo ha un costruttore statico. Quindi, sta succedendo qualcos'altro. –

+0

Hai ragione; il mio benchmark in realtà suggerisce che le prestazioni dei due casi nell'articolo siano più o meno equivalenti. – Zong

Problemi correlati