2011-02-01 7 views
15

Aggiornamento: come mi sarei aspettato, il solido consiglio della comunità in risposta a questa domanda era di "misurarlo e vedere". chibacity posted an answer con alcuni test davvero belli che hanno fatto questo per me; nel frattempo, ho scritto una mia prova; e la differenza di prestazioni che ho visto era in realtà così enorme che I felt compelled to write a blog post about it.Utilizzo di ThreadStatic per sostituire gli utenti locali costosi - buona idea?

Tuttavia, dovrebbe anche riconoscere che l'attributo Hans's explanationThreadStatic non è davvero libero e di fatto si basa su un metodo di CLR di supporto per lavorare la sua magia. Ciò rende tutt'altro che ovvio se si tratterebbe di un'ottimizzazione appropriata da applicare in caso arbitrario.

La buona notizia per me è che, nel caso mio, sembra aver fatto un grande miglioramento.


Ho un metodo che (tra le altre cose) un'istanza di alcuni array di medie dimensioni (~ 50 elementi) per un paio di variabili locali.

Dopo aver effettuato alcuni profili, ho identificato questo metodo come un collo di bottiglia per le prestazioni. Non è che il metodo richieda un tempo estremamente lungo per chiamare; piuttosto, è semplicemente chiamato molte volte, molto rapidamente (da centinaia di migliaia a milioni di volte in una sessione, che sarà di diverse ore). Quindi anche i miglioramenti relativamente piccoli alle sue prestazioni dovrebbero essere utili.

Mi è venuto in mente che forse invece di assegnare un nuovo array per ogni chiamata, potrei usare i campi contrassegnati con [ThreadStatic]; ogni volta che viene chiamato il metodo, controlla se il campo è inizializzato sul thread corrente e, in caso contrario, lo inizializza. Da quel punto in poi tutte le chiamate sullo stesso thread avranno un array tutto pronto per andare in quel punto.

(Il metodo inizializza ogni elemento della matrice stessa, in modo da avere elementi "stantio" nella matrice non dovrebbe essere un problema.)

La mia domanda è semplicemente questa: Ti sembra una buona idea? Ci sono dei problemi nell'usare l'attributo ThreadStatic in questo modo (ad esempio, come ottimizzazione delle prestazioni per mitigare il costo dell'istanziazione di nuovi oggetti per le variabili locali) di cui dovrei essere a conoscenza? Forse le prestazioni di un campo ThreadStatic non sono grandiose; ad esempio, c'è un sacco di "cose" in più in background, con il suo insieme di costi, per rendere possibile questa funzione?

E 'anche abbastanza plausibile a me che ho sbagliato a cercare anche di ottimizzare qualcosa a buon mercato (?) Come un 50-elemento della matrice, e se è così, sicuramente me lo faccia sapere, ma il generale domanda ancora detiene.

+1

Provalo e misura. Suppongo che l'accesso a un oggetto locale thread non sia libero, anche se forse più economico della riallocazione. – 9000

+0

Provalo e misuralo. – BRampersad

+0

Se si è in grado di utilizzare .NET4, non dimenticare ['ThreadLocal '] (http://msdn.microsoft.com/en-us/library/dd642243.aspx). La giuria è ancora fuori se non ha superato 'ThreadStatic', ma è un po 'più facile da usare (e avere ragione). Ti consigliamo di considerare 'ThreadLocal ' e includerlo nei tuoi benchmark. – LukeH

risposta

5

Ho eseguito un semplice benchmark e ThreadStatic si comporta meglio per i semplici parametri descritti nella domanda.

Come con molti algoritmi che hanno un elevato numero di iterazioni, sospetto che è un caso semplice di GC sovraccarico uccidere per la versione che assegna nuovi array:

Modifica

Con prove che includere un'iterazione aggiunta della matrice per modellare uso riferimento matrice minimo, più ThreadStatic utilizzo riferimento matrice oltre alla prova precedente in cui domanda è stata copiata locale:

Iterations : 10,000,000 

Local ArrayRef   (- array iteration) : 330.17ms 
Local ArrayRef   (- array iteration) : 327.03ms 
Local ArrayRef   (- array iteration) : 1382.86ms 
Local ArrayRef   (- array iteration) : 1425.45ms 
Local ArrayRef   (- array iteration) : 1434.22ms 
TS CopyArrayRefLocal (- array iteration) : 107.64ms 
TS CopyArrayRefLocal (- array iteration) : 92.17ms 
TS CopyArrayRefLocal (- array iteration) : 92.42ms 
TS CopyArrayRefLocal (- array iteration) : 92.07ms 
TS CopyArrayRefLocal (- array iteration) : 92.10ms 
Local ArrayRef   (+ array iteration) : 1740.51ms 
Local ArrayRef   (+ array iteration) : 1647.26ms 
Local ArrayRef   (+ array iteration) : 1639.80ms 
Local ArrayRef   (+ array iteration) : 1639.10ms 
Local ArrayRef   (+ array iteration) : 1646.56ms 
TS CopyArrayRefLocal (+ array iteration) : 368.03ms 
TS CopyArrayRefLocal (+ array iteration) : 367.19ms 
TS CopyArrayRefLocal (+ array iteration) : 367.22ms 
TS CopyArrayRefLocal (+ array iteration) : 368.20ms 
TS CopyArrayRefLocal (+ array iteration) : 367.37ms 
TS TSArrayRef  (+ array iteration) : 360.45ms 
TS TSArrayRef  (+ array iteration) : 359.97ms 
TS TSArrayRef  (+ array iteration) : 360.48ms 
TS TSArrayRef  (+ array iteration) : 360.03ms 
TS TSArrayRef  (+ array iteration) : 359.99ms 

Codice:

[ThreadStatic] 
private static int[] _array; 

[Test] 
public object measure_thread_static_performance() 
{ 
    const int TestIterations = 5; 
    const int Iterations = (10 * 1000 * 1000); 
    const int ArraySize = 50; 

    Action<string, Action> time = (name, test) => 
    { 
     for (int i = 0; i < TestIterations; i++) 
     { 
      TimeSpan elapsed = TimeTest(test, Iterations); 
      Console.WriteLine("{0} : {1:F2}ms", name, elapsed.TotalMilliseconds); 
     } 
    }; 

    int[] array = null; 
    int j = 0; 

    Action test1 =() => 
    { 
     array = new int[ArraySize]; 
    }; 

    Action test2 =() => 
    { 
     array = _array ?? (_array = new int[ArraySize]); 
    }; 

    Action test3 =() => 
    { 
     array = new int[ArraySize]; 

     for (int i = 0; i < ArraySize; i++) 
     { 
      j = array[i]; 
     } 
    }; 

    Action test4 =() => 
    { 
     array = _array ?? (_array = new int[ArraySize]); 

     for (int i = 0; i < ArraySize; i++) 
     { 
      j = array[i]; 
     } 
    }; 

    Action test5 =() => 
    { 
     array = _array ?? (_array = new int[ArraySize]); 

     for (int i = 0; i < ArraySize; i++) 
     { 
      j = _array[i]; 
     } 
    }; 

    Console.WriteLine("Iterations : {0:0,0}\r\n", Iterations); 
    time("Local ArrayRef   (- array iteration)", test1); 
    time("TS CopyArrayRefLocal (- array iteration)", test2); 
    time("Local ArrayRef   (+ array iteration)", test3); 
    time("TS CopyArrayRefLocal (+ array iteration)", test4); 
    time("TS TSArrayRef  (+ array iteration)", test5); 

    Console.WriteLine(j); 

    return array; 
} 

[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", MessageId = "System.GC.Collect")] 
private static TimeSpan TimeTest(Action action, int iterations) 
{ 
    Action gc =() => 
    { 
     GC.Collect(); 
     GC.WaitForFullGCComplete(); 
    }; 

    Action empty =() => { }; 

    Stopwatch stopwatch1 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) 
    { 
     empty(); 
    } 

    TimeSpan loopElapsed = stopwatch1.Elapsed; 

    gc(); 
    action(); //JIT 
    action(); //Optimize 

    Stopwatch stopwatch2 = Stopwatch.StartNew(); 

    for (int j = 0; j < iterations; j++) action(); 

    gc(); 

    TimeSpan testElapsed = stopwatch2.Elapsed; 

    return (testElapsed - loopElapsed); 
} 
+0

Probabilmente ci dovrebbe essere qualche uso degli array per rendere questo un confronto realistico. –

+0

@ 500 Buon punto - ma puoi vedere un problema specifico? –

+0

Devo ancora usare [ThreadStatic] da solo, quindi non sono un esperto, ma il suo equivalente nel codice non gestito ha un sovraccarico per ogni accesso. Forse questo può essere mitigato qui copiando la [ThreadStatic] in un locale prima dell'uso, ma sarebbe interessante vedere alcuni numeri. –

2

Da risultati come this, ThreadStatic sembra piuttosto veloce. Non sono sicuro che qualcuno abbia una risposta specifica se è più veloce, quindi riallocare un array di 50 elementi. Questo è il tipo di cose che dovrai fare da te. :)

Sono un po 'lacerato se è una "buona idea" oppure no. Fintanto che tutti i dettagli dell'implementazione sono mantenuti all'interno della classe, non è necessariamente una cattiva idea (davvero non vuoi che il chiamante debba preoccuparsene), ma a meno che i benchmark non mostrino un guadagno in termini di prestazioni con questo metodo, mi limiterei a allocare l'array ogni volta perché rende il codice più semplice e più facile da leggere. Essendo la più complicata delle due soluzioni, dovrei vedere alcuni benefici dalla complessità prima di scegliere questa.

8

[ThreadStatic] è no pranzo libero. Ogni accesso alla variabile deve passare attraverso una funzione di supporto nel CLR (JIT_GetThreadFieldAddr_Primitive/Objref) invece di essere compilato in linea dal jitter. Inoltre non è un vero sostituto per una variabile locale, la ricorsione sta per byte. Devi davvero definire questo te stesso, non è fattibile ritestare perf con quel tanto codice CLR nel ciclo.

+1

Devi pagare solo una volta per chiamata di funzione (per prendere un riferimento alla matrice e memorizzarlo in un locale reale) – Yuliy

+0

No, non è neanche un pranzo gratis. Il riferimento all'oggetto passa ancora attraverso uno stub. –

Problemi correlati