2011-01-12 14 views
73

He estado experimentando con multi-threading y procesamiento paralelo y necesitaba un contador para hacer un recuento básico y un análisis estadístico de la velocidad del procesamiento. Para evitar problemas con el uso concurrente de mi clase que he usado un comunicado bloqueo en una variable privada de mi clase:¿Qué tan caro es el estado de bloqueo?

private object mutex = new object(); 

public void Count(int amount) 
{ 
lock(mutex) 
{ 
    done += amount; 
} 
} 

Pero me preguntaba ... ¿Cuánto cuesta el bloqueo de una variable? ¿Cuáles son los efectos negativos en el rendimiento?

+7

Bloquear la variable no es tan caro; es la espera de una variable bloqueada que desea evitar. – Gabe

+34

es mucho menos costoso que pasar horas buscando otra condición de carrera ;-) – BrokenGlass

+1

Bueno ... si un candado es costoso, es posible que desee evitarlos cambiando la programación para que necesite menos bloqueos. Podría implementar algún tipo de sincronización. –

Respuesta

63

Aquí está an article que va en el costo. La respuesta corta es 50ns.

+1

Por lo tanto, en conclusión, cuantos más objetos tenga, más caro se vuelve. –

+13

Respuesta mejor breve: 50ns + tiempo dedicado a esperar si otro hilo mantiene el bloqueo. – Herman

+2

Cuantos más hilos están entrando y saliendo de bloqueo, más costoso se vuelve. El costo se expande exponencialmente con el número de subprocesos –

17

Esto no responde a su pregunta sobre el rendimiento, pero puedo decir que el .NET Framework es que ofrece un método Interlocked.Add que permitirá que usted agregue su amount a su miembro done sin bloquear manualmente en otro objeto.

+1

Sí, esta es probablemente la mejor respuesta. Pero principalmente por razones de código más corto y limpio. La diferencia de velocidad probablemente no sea notable. –

+0

gracias por esta respuesta. Estoy haciendo más cosas con cerraduras. Ints añadidos es uno de muchos. Me encanta la sugerencia, la usaré a partir de ahora. –

+0

bloqueos son mucho, mucho más fáciles de hacer bien, incluso si el código de bloqueo es potencialmente más rápido. Interlocked.Add tiene los mismos problemas que + = sin sincronización. – hangar

9

lock (Monitor.Enter/Exit) es muy económico, más barato que otras alternativas como Waithandle o Mutex.

Pero, ¿y si fuera (un poco) lento, preferiría tener un programa rápido con resultados incorrectos?

+4

Jaja ... Iba por el programa rápido y los buenos resultados. –

+0

@ henk-holterman Hay múltiples problemas con sus estados de cuenta: ** Primero ** como esta pregunta y respuestas mostraron claramente, hay una comprensión baja de los impactos del bloqueo en el rendimiento general, incluso personas que dicen mito acerca de 50ns que solo es aplicable con entorno de un solo hilo. ** Segundo ** su declaración está aquí y permanecerá durante años y en el tiempo medio, los procesadores crecerán en núcleos, pero la velocidad de los núcleos no lo es tanto. ** Las aplicaciones Thrid ** se vuelven más complejas con el tiempo, y entonces es capa sobre capa de bloqueo en el entorno de muchos núcleos y el número va en aumento, 2,4,8,10,20,16,32 – ipavlu

+0

Mi enfoque habitual es construir la sincronización de forma ligeramente acoplada con la menor interacción posible. Eso va muy rápido a estructuras de datos sin bloqueo. Hice mis envoltorios de código alrededor de spinlock para simplificar el desarrollo e incluso cuando TPL tiene colecciones simultáneas especiales, he desarrollado colecciones de spinlock de mi propia lista, array, diccionario y cola, ya que necesitaba un poco más de control y algunas veces código ejecutado bajo spinlock. Puedo decir que es posible y permite resolver múltiples escenarios que las colecciones de TPL no pueden hacer y con un gran rendimiento/ganancia de rendimiento. – ipavlu

4

Hay algunas formas diferentes de definir el "costo". Existe la sobrecarga real de obtener y liberar la cerradura; como Jake escribe, eso es insignificante a menos que esta operación se realice millones de veces.

De mayor relevancia es el efecto que esto tiene en el flujo de ejecución. Este código solo se puede ingresar por un hilo a la vez. Si tiene 5 hilos que realizan esta operación de forma regular, 4 de ellos terminarán esperando a que se libere el bloqueo, y luego será el primer hilo programado para ingresar ese fragmento de código después de que se libere ese bloqueo. Por lo tanto, su algoritmo va a sufrir significativamente. Cuánto depende del algoritmo y con qué frecuencia se llama la operación. No se puede evitar sin introducir condiciones de carrera, pero se puede mejorar minimizando la cantidad de llamadas al código bloqueado.

43

La respuesta técnica es que es imposible de cuantificar, depende en gran medida del estado de los búferes de recuperación de memoria de la CPU y de la cantidad de datos que el capturador previo debe descartarse y volverse a leer. Que son ambos muy no deterministas. Utilizo 150 ciclos de CPU como una aproximación de respaldo del envolvente que evita grandes decepciones.

La respuesta práctica es que es waaaay más económico que la cantidad de tiempo que quemará al depurar su código cuando crea que puede omitir un bloqueo.

Para obtener un número difícil tendrá que medir. Visual Studio tiene una resolución concurrency analyzer disponible como extensión.

+1

Realmente no, se puede cuantificar y medir. Simplemente no es tan fácil como escribir esos bloqueos en todo el código, y luego decir que solo son 50, un mito medido en el acceso de un solo hilo a la cerradura. – ipavlu

+4

* "Creo que puedes saltarte un candado" * ... Creo que es ahí donde mucha gente está cuando lee esta pregunta ... – Snoopy

6

El costo de un bloqueo en un circuito cerrado, en comparación con una alternativa sin bloqueo, es enorme. Puede darse el lujo de repetir muchas veces y aún ser más eficiente que un bloqueo. Es por eso que las colas sin bloqueo son tan eficientes.

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace LockPerformanceConsoleApplication 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var stopwatch = new Stopwatch(); 
      const int LoopCount = (int) (100 * 1e6); 
      int counter = 0; 

      for (int repetition = 0; repetition < 5; repetition++) 
      { 
       stopwatch.Reset(); 
       stopwatch.Start(); 
       for (int i = 0; i < LoopCount; i++) 
        lock (stopwatch) 
         counter = i; 
       stopwatch.Stop(); 
       Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds); 

       stopwatch.Reset(); 
       stopwatch.Start(); 
       for (int i = 0; i < LoopCount; i++) 
        counter = i; 
       stopwatch.Stop(); 
       Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds); 
      } 

      Console.ReadKey(); 
     } 
    } 
} 

Salida:

With lock: 2013 
Without lock: 211 
With lock: 2002 
Without lock: 210 
With lock: 1989 
Without lock: 210 
With lock: 1987 
Without lock: 207 
With lock: 1988 
Without lock: 208 
+3

Esto podría ser un mal ejemplo porque tu loop realmente no hace nada, aparte desde una sola asignación de variable y un bloqueo es al menos 2 llamadas a funciones. Además, 20ns por bloqueo que recibes no es tan malo. –

22

Oh cielos!

¡Parece que la respuesta correcta señalada aquí como LA RESPUESTA es inherentemente incorrecta! Me gustaría pedirle al autor de la respuesta, respetuosamente, que lea el artículo vinculado hasta el final.article

El autor del artículo de 2003 article fue la medición en la máquina de doble núcleo y sólo en el primer caso de medición, se mide bloqueo con un solo hilo solamente y el resultado fue de aproximadamente 50 ns por acceso a la cerradura.

No dice nada sobre un bloqueo en el entorno concurrente. Así que tenemos que seguir leyendo el artículo y en la segunda mitad el autor estaba midiendo el escenario de bloqueo con dos y tres hilos, lo que se acerca más a los niveles de concurrencia de los procesadores de hoy.

Así que el autor dice que, con dos hilos en Dual Core, los bloqueos cuestan 120ns, y con 3 hilos va a 180ns. Por lo tanto, parece ser claramente dependiente del número de subprocesos accedidos simultáneamente y más es peor.

Así que es simple, no es 50 ns, a menos que sea un solo hilo, donde la cerradura se vuelve inútil.

Otro tema a tener en cuenta es que se mide como tiempo promedio!

Si se midiera el tiempo de iteraciones, incluso habría tiempos entre 1ms y 20ms, simple porque la mayoría era rápida, pero pocos hilos esperarán el tiempo de los procesadores e incluso retrasos de milisegundos.

Estas son malas noticias para cualquier tipo de aplicación que requiera un alto rendimiento, baja latencia.

Y el último problema a tener en cuenta es que podría haber operaciones más lentas dentro del bloqueo y muy a menudo ese es el caso. Cuanto más tiempo se ejecuta el bloque de código dentro de la cerradura, mayor es la disputa y los retrasos se elevan a gran altura.

Tenga en cuenta que ya ha pasado más de una década desde 2003, es decir, algunas generaciones de procesadores diseñados específicamente para funcionar de manera simultánea y el bloqueo está perjudicando considerablemente el rendimiento de los mismos.

Cuestiones relacionadas