2011-08-20 13 views
9

He escrito un pequeño programa de prueba y me sorprendió por qué la solución lock {} funciona más rápido que sin bloqueo pero con el atributo [ThreadStatic] sobre la variable estática..NET: ThreadStatic vs lock {}. ¿Por qué ThreadStaticAttribute degrada el rendimiento?

[ThreadStatic] fragmento:

[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); 
} 

bloqueo {} fragmento:

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); 
} 

En mi máquina primer fragmento toma 4,2 segundos; segundo - 3.2 segundos, que es 1 segundo más rápido. Sin ThreadStatic y bloqueo: 1,2 segundos.

Tengo curiosidad por qué el atributo [ThreadStatic] en este sencillo ejemplo agrega tantos al tiempo de ejecución del programa?

ACTUALIZACIÓN: Lo siento mucho, pero estos resultados son para DEBUG compilación. Para RELEASE, obtuve números completamente diferentes: (1.2; 2.4; 1.2). Para DEBUG los números fueron (4.2; 3.2; 1.2).

Por lo tanto, para RELEASE construir no parece haber [ThreadStatic] de rendimiento de la pena de rendimiento.

+0

Eso no es lo que veo en mi QuadCore. Si compilo Release on Any Cpu obtengo 0.81s para el primer fragmento y 4.5s para el segundo.El caso trivial sin seguridad de hilo toma 0.46s. – FuleSnabel

+0

Tienes razón. Cambié del modo 'Debug' al' Release' y obtuve resultados similares a los tuyos. – Roman

+0

Puede ayudar leer el código desmontado para comprender por qué obtiene resultados sorprendentes. – FuleSnabel

Respuesta

6

Para la versión RELEASE parece que casi no hay una penalización de rendimiento [ThreadStatic] (solo una pequeña penalización en las CPU modernas).

Aquí viene el código de ensamblaje para ms_Acc += one; RELEASE de optimización está habilitado:

Sin[ThreadStatic], DEBUG:

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

Sin[ThreadStatic], RELEASE:

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

[ThreadStatic], DEBUG:

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

Parece que [ThreadStatic], DEBUG es de 32 bits y [ThreadStatic], RELEASE es de 64 bits. – kerem

-1

Tiene dos líneas de código que actualizan ms_Acc. En el caso lock, tiene un bloqueo único alrededor de ambos, mientras que en el caso ThreadStatic, ocurre una vez para cada acceso a ms_Acc, es decir, dos veces para cada iteración de su ciclo. Este es generalmente el beneficio de usar lock, puede elegir la granularidad que desee. Supongo que la compilación RELEASE optimizó esta diferencia.

Me interesaría ver si el rendimiento es muy similar, o idéntico, si cambia el ciclo for a un único acceso al ms_Acc.

+0

No estoy seguro de que la ralentización se deba a dos líneas de código que actualizan 'ms_Acc'. Agregué 'ms_Acc/= one' solamente porque' ms_Acc + = one' es increíblemente rápido y hace que sea difícil medir el tiempo. No creo que '[ThreadStatic]' de alguna manera sincronice el acceso a la variable; su propósito es evitar la sincronización (porque otros hilos "no pueden ver" la variable '[ShreadStatic]') y, por lo tanto, aumentar el rendimiento. – Roman

Cuestiones relacionadas