thread safe example current concurrent collection addorupdate c# .net-4.0 task-parallel-library concurrentdictionary

c# - safe - ConcurrentDictionary<> rendimiento en un solo hilo malentendido?



concurrentdictionary c# example (6)

Información breve relacionada:

AFAIK, las clases de pila, cola y bolsa concurrentes se implementan internamente con listas vinculadas.
Y sé que hay mucha menos contención porque cada hilo es responsable de su propia lista vinculada. De cualquier manera, mi pregunta es sobre el ConcurrentDictionary<,>

Pero estaba probando este código: (hilo único)

Stopwatch sw = new Stopwatch(); sw.Start(); var d = new ConcurrentDictionary < int, int > (); for(int i = 0; i < 1000000; i++) d[i] = 123; for(int i = 1000000; i < 2000000; i++) d[i] = 123; for(int i = 2000000; i < 3000000; i++) d[i] = 123; Console.WriteLine("baseline = " + sw.Elapsed); sw.Restart(); var d2 = new Dictionary < int, int > (); for(int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123; for(int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123; for(int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123; Console.WriteLine("baseline = " + sw.Elapsed); sw.Stop();

Resultado: (probado muchas veces, mismos valores (+/-)).

baseline = 00:00:01.2604656 baseline = 00:00:00.3229741

Pregunta:

¿Qué hace que ConcurrentDictionary<,> mucho más lento en un entorno de un solo hilo?

Mi primer instinto es que lock(){} siempre será más lento. Pero al parecer no lo es.


Diccionario Concurrente vs.

En general, use un System.Collections.Concurrent.ConcurrentDictionary en cualquier escenario en el que agregue y actualice claves o valores de forma simultánea desde varios subprocesos. En escenarios que involucran actualizaciones frecuentes y relativamente pocas lecturas, el ConcurrentDictionary generalmente ofrece beneficios modestos. En escenarios que involucran muchas lecturas y muchas actualizaciones, el ConcurrentDictionary generalmente es significativamente más rápido en las computadoras que tienen cualquier número de núcleos.

En escenarios que involucran actualizaciones frecuentes, puede aumentar el grado de concurrencia en el ConcurrentDictionary y luego medir para ver si el rendimiento aumenta en las computadoras que tienen más núcleos. Si cambia el nivel de concurrencia, evite las operaciones globales tanto como sea posible.

Si solo está leyendo la clave o los valores, el Diccionario es más rápido porque no se requiere sincronización si el subproceso no está siendo modificado por ningún hilo.

Enlace: https://msdn.microsoft.com/en-us/library/dd997373%28v=vs.110%29.aspx


Acabo de escribir sobre mi implementación de diccionario de escritura y escritura segura para subprocesos sin bloqueo aquí:

http://www.singulink.com/CodeIndex/post/fastest-thread-safe-lock-free-dictionary

Es muy rápido para ráfagas rápidas de escrituras y búsquedas que generalmente se ejecutan a una velocidad de Dictionary estándar del 100% sin bloqueo. Si escribe ocasionalmente y lee con frecuencia, esta es la opción más rápida disponible.

Actualmente solo se puede agregar, ya que lo usé para el almacenamiento en caché, pero si existe una demanda popular, también podría agregarle métodos de eliminación.

ConcurrentDictionary es lento porque elimina bloqueos de lectura / escritura en cada operación. Los bloqueos de lectura / escritura son incluso más lentos que los bloqueos normales, pero permiten múltiples lectores sin bloqueo.

Mi implementación ofrece el máximo rendimiento de lectura al eliminar la necesidad de bloqueos de lectura en circunstancias normales, mientras que las actualizaciones no se realizan en el diccionario. La compensación es que el diccionario debe copiarse e intercambiarse después de que se apliquen las actualizaciones (esto se hace en un hilo en segundo plano) pero si no escribe con frecuencia o solo escribe una vez durante la inicialización, entonces definitivamente vale la pena la compensación eso.


Bueno, ConcurrentDictionary está permitiendo la posibilidad de que pueda ser usado por múltiples hilos. Me parece del todo razonable que eso requiera más limpieza interna que algo que asuma que puede escapar sin preocuparse por el acceso desde varios subprocesos. Me habría sorprendido mucho si hubiera funcionado al revés. Si la versión más segura siempre fuera más rápida , ¿por qué utilizaría la versión menos segura?


El ConcurrentDictionary<> crea un conjunto interno de objetos de bloqueo en la creación (esto se determina por el nivel de concurrencyLevel , entre otros factores): este conjunto de objetos de bloqueo se usa para controlar el acceso a las estructuras internas del cazo en una serie de bloqueos de grano fino.

En un escenario de un solo subproceso, no habría necesidad de los bloqueos, por lo que la sobrecarga adicional de adquirir y liberar estos bloqueos es probablemente la fuente de la diferencia que está viendo.


La razón más probable por la que ConcurrentDictionary simplemente tiene más gastos que el Dictionary para la misma operación. Esto es demostrablemente cierto si profundizas en las fuentes

  • Utiliza un bloqueo para el indexador.
  • Utiliza escrituras volátiles.
  • Tiene que hacer escrituras atómicas de valores que no están garantizados para ser atómicos en .Net
  • Tiene ramas adicionales en la rutina de adición de núcleo (ya sea para bloquear, hacer una escritura atómica)

Todos estos costos se incurren independientemente de la cantidad de subprocesos en los que se está utilizando. Estos costos pueden ser individualmente pequeños pero no son gratuitos y se acumulan con el tiempo


Tu prueba es incorrecta: debes detener el cronómetro antes.

Stopwatch sw = new Stopwatch(); sw.Start(); var d = new ConcurrentDictionary<int, int>(); for (int i = 0; i < 1000000; i++) d[i] = 123; for (int i = 1000000; i < 2000000; i++) d[i] = 123; for (int i = 2000000; i < 3000000; i++) d[i] = 123; sw.Stop(); Console.WriteLine("baseline = " + sw.Elapsed); sw.Start(); var d2 = new Dictionary<int, int>(); for (int i = 0; i < 1000000; i++) lock (d2) d2[i] = 123; for (int i = 1000000; i < 2000000; i++) lock (d2) d2[i] = 123; for (int i = 2000000; i < 3000000; i++) lock (d2) d2[i] = 123; sw.Stop(); Console.WriteLine("baseline = " + sw.Elapsed); sw.Stop();

- Salida: