BeforeFieldInit … un type attribute invisibile ma determinante !

L’inizializzazione dei campi statici di una classe può essere eseguita con le due seguenti diverse modalità oppure con un mix di esse :

  • Type Initializer : inizializzazione di un campo statico in corrispondenza della sua dichiarazione;
  • Type Constructor : costruttore statico della classe che viene invocato una sola volta (per application domain) sulla stessa;

Ebbene, la presenza o meno dell’uno o dell’altro può produrre sequenze di esecuzioni differenti da parte del CLR sulla nostra classe.

Consideriamo le due seguenti classi, che a prima vista risultano semanticamente uguali :

class FirstClass
{
// type initializer
public static int x = 1;
}

class SecondClass
{
public static int x;

// type constructor
static SecondClass()
{
x = 1;
}
}

Entrambe hanno il campo statico “x”  ma che viene inizializzato in maniera diversa. Con unType Initializer nel primo caso e con un costruttore statico (Type Constructor) nel secondo. Il risultato finale sarà ovviamente lo stesso ma, se utilizziamo il Reflector oppure ILDASM, constatiamo come il compilatore abbia tradotto in codice IL le due classi in maniera leggerment diversa :

3056.beforefieldinit_1_57F8FD6D

0486.beforefieldinit_2_thumb_542B32D0

Alla classe che non ha il costruttore statico è stato aggiunto l’attributo beforefieldinit.
Cosa comporta la presenza di questo attributo ?
E’ noto che, nell’ambito di una stessa classe, l’invocazione di un Type Initializer avviene sempre prima del Type Constructor ed inoltre, l’inizializzazione di un campo statico di una classe viene eseguita ovviamente prima che si acceda a tale campo. Ma quanto prima ? Siamo certi che avviene immediatamente prima ? O magari viene eseguita molto tempo prima ? Se tentassimo di accedere al campo “x”  di ciascuna delle due classi, esso sarà stato inizializzato immediatamente prima dell’accesso o molto tempo prima ?
Per rendercene conto, consideriamo le due seguenti classi :
class BeforeFieldInit
{
    // type initializer
    public static string x = WriteLine("BeforeFieldInit Type Initializer");

    // static method
    public static string WriteLine(string s)
    {
        Console.WriteLine(s);
        return s;
    }
}

class NotBeforeFieldInit
{
    public static string x;

    // type constructor
    static NotBeforeFieldInit()
    {
        x = WriteLine("NotBeforeFieldInit Static Constructor");
    }

    // static method
    public static string WriteLine(string s)
    {
        Console.WriteLine(s);
        return s;
    }
}

 

Entrambe hanno un metodo statico per la scrittura di una stringa su console ma, mentre la prima sarà compilata con l’attributo beforefieldinit, la seconda non sarà dotata di questo attributo.
Considerando la seguente applicazione delle due classi suddette ed osserviamone l’output :

static void Main(string[] args)
{
    string tmp;
    Console.WriteLine("Starting Main");
    tmp = BeforeFieldInit.x;
    tmp = NotBeforeFieldInit.x;
    Console.ReadLine();
}
1033.beforefieldinit_3_41763919

Nonostante l’accesso al campo “x” della classe BeforeFieldInit sia l’istruzione 5, il Type Initializer viene invocato addirittura prima dell’istruzione 4. Mentre il costruttore statico della classe NotBeforeFieldInit viene invocato immediatamente prima dell’accesso al campo.

Ciò fa capire che la presenza dell’attributo beforefieldinit (aggiunto dal compilatore quando una classe non è dotato esplicitamente di costruttore statico), indica al CLR di poter invocare l’inizializzazione dei campi statici anche tempo prima che ci sia un accesso su di essi mentre l’assenza dell’attributo ritarda l’inizializzazione immediatamente prima all’accesso.

Conseguenze sulle performance

A questo punto, vediamo come questa semplice differenza possa comportare un impatto sulle performance di un’applicazione.

Consideriamo nuovamente le due classi FirstClass e SecondClass precedenti, dotate entrambe di un campo statico intero “x” ma la prima, ovviamente compilata con l’attributobeforefieldinit. Utilizziamole nel seguente applicativo di esempio :

static void PerformanceTest1()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        FirstClass.x = 1;
    }
    Console.WriteLine("Performance access ... {0} : FirstClass", sw.Elapsed);

    sw = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        SecondClass.x = 1;
    }
    Console.WriteLine("Performance access ... {0} : SecondClass", sw.Elapsed);
}

static void Main(string[] args)
{
    PerformanceTest1();
    Console.ReadLine();
}

Vogliamo valutare il tempo necessario per eseguire 100 assegnazioni consecutive sul campo statico “x” di ciascuna classe. Il risultato è il seguente (i tempi possono ovviamente variare da computer a computer e tra un avvio e l’altro, ma il rapporto rimarrà comunque lo stesso) :

1033.beforefieldinit_3_41763919

Utilizzando la classe compilata con l’attributo beforefieldinit, abbiamo un miglioramento di performance dell’80% circa (5 volte più veloce) !!

A cosa è dovuta questa notevole differenza ?

Quando il JIT compiler compila il metodo PerformanceTest1() produce un codice macchina leggermente diverso :

  • nel caso di utilizzo della FirstClass, il Type Initializer viene invocato prima dell’ingresso del ciclo for;
  • nel caso di utilizzo della SecondClass, la chiamata al Type Constructor viene inserita all’interno del ciclo for ma ovviamente il costruttore statico dovrà essere invocato una sola volta (alla prima iterazione). Per questo motivo, il JIT compiler produce un codice che esegue un check per verificare se tale costruttore sia stato o meno invocato; ciò determina un peggioramento delle performance !

Per rendercene conto, riporto di seguito il codice macchina (mixato con le istruzioni C#) prodotto dal JIT compiler :

7563.beforefieldinit_5_7DDE87F6

Nel caso della FirstClass, constatiamo la presenza della sola istruzione che assegna il valore 1 al campo statico “x” della classe stessa.

2313.beforefieldinit_6_423E7936

Nel caso della SecondClass, prima dell’istruzione di assegnazione del valore 1 al campo statico “x”, notiamo la presenza di una call. Con questa call, viene invocato il costruttore statico e viene eseguito il check che verifica, quindi ad ogni iterazione, se sulla classeSecondClass il costruttore stesso sia stato già invocato in precedenza.

Aggiungiamo un’altra funzione al nostro esempio che verrà invocata nel main :

static void PerformanceTest2()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        FirstClass.x = 1;
    }
    Console.WriteLine("Performance access ... {0} : FirstClass", sw.Elapsed);

    sw = Stopwatch.StartNew();
    for (int i = 0; i < 1000; i++)
    {
        SecondClass.x = 1;
    }
    Console.WriteLine("Performance access ... {0} : SecondClass", sw.Elapsed);
}

static void Main(string[] args)
{
    PerformanceTest1();
    PerformanceTest2();
    Console.ReadLine();
}

Osserviamo che la funzione PerformanceTest2() è volutamente uguale allaPerformanceTest1() e viene invocata subito dopo di essa nel main. Non ho utilizzato la stessa funzione chiamandola due volte, perché voglio forzare il JIT compiler ad eseguire una nuova compilazione (nel caso della doppia chiamata alla PerformanceTest1() ciò non accadrebbe). L’output sarà il seguente :

0830.beforefieldinit_7_5125A850

Eseguendo la funzione PerformanceTest2(), nonostante sia comunque diversa dallaPerformanceTest1() (anche se il codice è lo stesso), si osserva che le performance sono praticamente le stesse. Cosa è successo ? Semplicemente questo…

Quando il JIT compiler esegue la compilazione della funzione PerformanceTest2(), sa già che il costruttore statico della SecondClass è stato invocato (nella funzione PerformanceTest1()) e non genera il codice macchina che invoca il costruttore ed il check di verifica ad ogni iterazione.

5826.beforefieldinit_8_55BBB30A

Per quanto riguarda la classe FirstClass non è cambiato ovviamente nulla.

2046.beforefieldinit_9_3E40FED9

Viceversa, per la SecondClass non esiste più la call al costruttore statico della classe.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s