2011-08-20 7 views
9

Ho scritto un programma di test di dimensioni ridotte e sono rimasto sorpreso del motivo per cui la soluzione lock {} è più veloce di lock-free ma con attributo [ThreadStatic] su variabile statica..NET: ThreadStatic vs lock {}. Perché ThreadStaticAttribute riduce le prestazioni?

[ThreadStatic] frammento:

[ThreadStatic] 
private static long ms_Acc; 
public static void RunTest() 
{ 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    int one = 1; 
    for (int i = 0; i < 100 * 1000 * 1000; ++i) { 
     ms_Acc += one; 
     ms_Acc /= one; 
    } 
    stopwatch.Stop(); 
    Console.WriteLine("Time taken: {0}", stopwatch.Elapsed.TotalSeconds); 
} 

blocco {} frammento:

private static long ms_Acc; 
private static object ms_Lock = new object(); 
public static void RunTest() 
{ 
    Stopwatch stopwatch = new Stopwatch(); 
    stopwatch.Start(); 
    int one = 1; 
    for (int i = 0; i < 100 * 1000 * 1000; ++i) { 
     lock (ms_Lock) { 
      ms_Acc += one; 
      ms_Acc /= one; 
     } 
    } 
    stopwatch.Stop(); 
    Console.WriteLine("Time taken: {0}", stopwatch.Elapsed.TotalSeconds); 
} 

Sulla mia macchina prima frammento richiede 4,2 secondi; secondo - 3,2 secondi, che è 1 secondo più veloce. Senza ThreadStatic e blocco - 1,2 secondi.

Sono curioso perché l'attributo [ThreadStatic] in questo semplice esempio ne aggiunge così tanti per programmare il tempo di esecuzione?

UPDATE: Mi dispiace molto, ma questi risultati sono per la build DEBUG. Per RELEASE uno ho ottenuto numeri completamente diversi: (1.2; 2.4; 1.2). Per i numeri DEBUG erano (4,2; 3,2; 1,2).

Quindi, per la build RELEASE sembra non esserci la penalità di prestazione [ThreadStatic].

+0

che non è quello che vedo sul mio QuadCore. Se compilo Release su Any Cpu ottengo 0.81s per il primo snippet e 4.5s per il secondo.Il caso banale senza sicurezza del filo richiede 0,46 secondi. – FuleSnabel

+0

Hai ragione. Sono passato da 'Debug' a' Release' mode e sono diventato simile ai tuoi risultati. – Roman

+0

Può aiutare a leggere il codice smontato per capire perché si ottengono risultati sorprendenti. – FuleSnabel

risposta

6

Per la build di RELEASE sembra esserci quasi nessuna penalità di prestazioni [ThreadStatic] (solo una leggera penalizzazione sulle CPU moderne).

Qui viene il codice di disassemblaggio per ms_Acc += one; per RELEASE ottimizzazione è attivata:

No[ThreadStatic], DEBUG:

00000060 mov   eax,dword ptr [ebp-40h] 
00000063 add   dword ptr ds:[00511718h],eax 

No[ThreadStatic], RELEASE:

00000051 mov   eax,dword ptr [00040750h] 
00000057 add   eax,dword ptr [rsp+20h] 
0000005b mov   dword ptr [00040750h],eax 

[ThreadStatic], DEBUG:

012.351.
00000066 mov   edx,1 
0000006b mov   ecx,4616E0h 
00000070 call  664F7450 
00000075 mov   edx,1 
0000007a mov   ecx,4616E0h 
0000007f mov   dword ptr [ebp-50h],eax 
00000082 call  664F7450 
00000087 mov   edx,dword ptr [eax+18h] 
0000008a add   edx,dword ptr [ebp-40h] 
0000008d mov   eax,dword ptr [ebp-50h] 
00000090 mov   dword ptr [eax+18h],edx 

[ThreadStatic], RELEASE:

00000058 mov   edx,1 
0000005d mov   rcx,7FF001A3F28h 
00000067 call  FFFFFFFFF6F9F740 
0000006c mov   qword ptr [rsp+30h],rax 
00000071 mov   rbx,qword ptr [rsp+30h] 
00000076 mov   ebx,dword ptr [rbx+20h] 
00000079 add   ebx,dword ptr [rsp+20h] 
0000007d mov   edx,1 
00000082 mov   rcx,7FF001A3F28h 
0000008c call  FFFFFFFFF6F9F740 
00000091 mov   qword ptr [rsp+38h],rax 
00000096 mov   rax,qword ptr [rsp+38h] 
0000009b mov   dword ptr [rax+20h],ebx 
+2

Sembra come [ThreadStatic], DEBUG è a 32 bit e [ThreadStatic], RELEASE è a 64 bit. – kerem

-1

Si dispone di due righe di codice che aggiornano ms_Acc. Nel caso lock, si ha un singolo blocco su entrambi, mentre nel caso ThreadStatic, accade una volta per ogni accesso a ms_Acc, vale a dire due volte per ogni iterazione del ciclo. Questo è generalmente il vantaggio dell'uso di lock, è possibile scegliere la granularità desiderata. Immagino che la versione RELEASE abbia ottimizzato questa differenza.

Sarei interessato a vedere se la prestazione diventa molto simile o identica, se si modifica il ciclo for in un singolo accesso a ms_Acc.

+0

Non sono sicuro che il rallentamento sia dovuto a due righe di codice che aggiornano 'ms_Acc'. Ho aggiunto 'ms_Acc/= one' solo perché' ms_Acc + = one' è follemente veloce e rende difficile misurare il tempo. Non penso che '[ThreadStatic]' sincronizzi in qualche modo l'accesso alla variabile - il suo scopo è di evitare la sincronizzazione (perché gli altri thread "non possono vedere" la variabile '' [ShreadStatic] 'e, thu, s aumentare le prestazioni. – Roman