¿Por qué el estado compartido degrada el rendimiento?

19

He estado trabajando bajo el principio de compartir nada de la programación concurrente. Esencialmente, todos mis hilos de trabajo tienen copias inmutables de solo lectura del mismo estado que nunca se comparte entre ellos ( incluso por referencia ). En términos generales, esto ha funcionado realmente bien.

Ahora, alguien ha introducido un caché singleton sin bloqueo ( por ejemplo, un diccionario estático ) al que todos los hilos están accediendo simultáneamente. Como el diccionario nunca se modifica después del inicio, no hay bloqueos. No ha habido problemas de seguridad de subprocesos, pero ahora hay una degradación del rendimiento.

La pregunta es ... dado que no hay bloqueos, ¿por qué la introducción de este singleton crea un éxito en el rendimiento? ¿Qué está sucediendo exactamente debajo de las sábanas que podría explicar esto?

Para confirmar, acceder a este nuevo singleton es el único cambio y puedo recrearlo de manera confiable simplemente comentando la llamada al caché.

JoeGeeky
fuente
8
¿Has señalado un perfilador en el código?
Timo Geusch
2
Es poco probable que la creación de perfiles responda a esta pregunta a menos que esté perfilando el CLR y posiblemente el kernel de Windows (no es una tarea fácil para el programador promedio).
Igby Largeman
1
@JoeGeeky Alrighty entonces, ¡supongo que lo único que puedo hacer por mí aquí es +1 y favorecer! Parece extraño ya que ambos están en el mismo nivel de direccionamiento indirecto, después de todo, y deben encajar en la caché del procesador de todas formas, etc ...
Max
2
FWIT Engendré un par de hilos y ejecuté algunos temporizadores. Ejecuté una clase, singleton, lockedSingleton y dict <string, string>. Después de la primera instancia de cada uno, las ejecuciones consecutivas tomaron aproximadamente 2000ns para cualquier objeto dado. El diccionario corrió 2 veces más lento, podría ser causado por el código del constructor ... es más lento que el bloqueo por sí mismo. Teniendo en cuenta todo el GC, el manejo del sistema operativo de la cola de subprocesos y otros gastos generales ... no estoy seguro de que uno pueda realmente responder esta pregunta. Pero, según mis resultados, no creo que el problema tenga que ver con Singletons. No si se implementa como en MSDN. Excluye las optimizaciones del compilador.
P.Brian.Mackey
1
@JoeGeeky: otro pensamiento: ¿el uso de la memoria caché agrega un nivel de indirección? Si se accede con frecuencia, perseguir un puntero a la derecha extra (o MSIL equiv) podría agregar algo de tiempo sobre una copia local menos indirecta.
sdg

Respuestas:

8

Podría ser que el estado inmutable comparte una línea de caché con algo mutable. En este caso, un cambio en el estado mutable cercano podría tener el efecto de forzar una resincronización de esta línea de caché en los núcleos, lo que podría ralentizar el rendimiento.

Aidan Cully
fuente
3
Esto suena como un false sharingescenario que estás describiendo. Para aislar eso, necesitaré perfilar el caché L2. Desafortunadamente, estos son tipos de referencia, por lo que agregar espacio de búfer no será una opción si esto es realmente lo que está sucediendo.
JoeGeeky
3

Me aseguraría de que los métodos Equals()y GetHashCode()de los objetos que utiliza como claves para el diccionario no tengan ningún efecto secundario inesperado que no sea compatible con subprocesos. Perfilar sería de gran ayuda aquí.

Si por casualidad sus claves son cadenas, entonces tal vez ahí lo tenga: se rumorea que las cadenas se comportan como objetos inmutables, pero en aras de ciertas optimizaciones, se implementan internamente de manera mutable, con todo lo que esto implica con respecto al subprocesamiento múltiple .

Intentaría pasar el diccionario a los hilos que lo usan como referencia regular en lugar de un solo tono para ver si el problema radica en la compartición o en el tono único del diccionario. (Eliminando las posibles causas).

También trataría con un en ConcurrentDictionarylugar de un regular Dictionarysolo en caso de que su uso arroje algunos resultados sorprendentes. Hay muchas cosas que se deben especular sobre el problema en cuestión si un ConcurrentDictionaryresultado es mucho mejor o peor que el habitual Dictionary.

Si ninguno de los puntos anteriores apunta al problema, entonces supongo que el rendimiento degradado es causado por algún tipo de disputa extraña entre el hilo de recolección de basura y el resto de sus hilos, ya que el recolector de basura está tratando de averiguar si los objetos en su diccionario deben ser eliminados o no, mientras sus hilos están accediendo a ellos.

Mike Nakis
fuente