¿Cuánto se lee de la ThreadLocal
variable más lento que del campo normal?
Más concretamente, ¿la creación de objetos simples es más rápida o más lenta que el acceso a ThreadLocal
variables?
Supongo que es lo suficientemente rápido para que tener una ThreadLocal<MessageDigest>
instancia sea mucho más rápido que crear una instancia de MessageDigest
cada vez. Pero, ¿eso también se aplica al byte [10] o al byte [1000], por ejemplo?
Editar: La pregunta es ¿qué sucede realmente cuando se ThreadLocal
reciben llamadas ? Si ese es solo un campo, como cualquier otro, entonces la respuesta sería "siempre es más rápido", ¿verdad?
Thread
s contienen un mapa hash (no sincronizado) donde la clave es elThreadLocal
objeto actualRespuestas:
Ejecutar evaluaciones comparativas no publicadas
ThreadLocal.get
toma alrededor de 35 ciclos por iteración en mi máquina. No mucho. En la implementación de Sun, un mapa hash de sondeo lineal personalizado enThread
mapasThreadLocal
de valores. Debido a que solo se accede a él mediante un único hilo, puede ser muy rápido.La asignación de objetos pequeños requiere un número similar de ciclos, aunque debido al agotamiento de la caché, es posible que obtenga cifras algo más bajas en un bucle cerrado.
MessageDigest
Es probable que la construcción sea relativamente cara. Tiene bastante estado y la construcción pasa por elProvider
mecanismo SPI. Es posible que pueda optimizar, por ejemplo, clonando o proporcionando elProvider
.El hecho de que sea más rápido almacenar en caché en un
ThreadLocal
lugar que crear no significa necesariamente que el rendimiento del sistema aumentará. Tendrá gastos generales adicionales relacionados con GC, lo que ralentiza todo.A menos que su aplicación utilice mucho,
MessageDigest
es posible que desee considerar el uso de una caché convencional segura para subprocesos.fuente
new org.bouncycastle.crypto.digests.SHA1Digest()
. Estoy bastante seguro de que ningún caché puede superarlo.En 2009, algunas JVM implementaron ThreadLocal utilizando un HashMap no sincronizado en el objeto Thread.currentThread (). Esto lo hizo extremadamente rápido (aunque no tan rápido como usar un acceso de campo regular, por supuesto), además de asegurar que el objeto ThreadLocal se arreglara cuando el Thread murió. Al actualizar esta respuesta en 2016, parece que la mayoría (¿todas?) De las JVM más nuevas usan un ThreadLocalMap con sondeo lineal. No estoy seguro del rendimiento de esos, pero no puedo imaginar que sea significativamente peor que la implementación anterior.
Por supuesto, el nuevo Object () también es muy rápido en estos días, y los recolectores de basura también son muy buenos para recuperar objetos de corta duración.
A menos que esté seguro de que la creación de objetos será costosa, o si necesita conservar algún estado hilo por hilo, es mejor optar por la solución de asignación más simple cuando sea necesario, y solo cambiar a una implementación ThreadLocal cuando un profiler le dice que lo necesita.
fuente
Buena pregunta, me lo he estado preguntando recientemente. Para darle números definidos, los puntos de referencia a continuación (en Scala, compilados virtualmente con los mismos códigos de bytes que el código Java equivalente):
var cnt: String = "" val tlocal = new java.lang.ThreadLocal[String] { override def initialValue = "" } def loop_heap_write = { var i = 0 val until = totalwork / threadnum while (i < until) { if (cnt ne "") cnt = "!" i += 1 } cnt } def threadlocal = { var i = 0 val until = totalwork / threadnum while (i < until) { if (tlocal.get eq null) i = until + i + 1 i += 1 } if (i > until) println("thread local value was null " + i) }
disponibles aquí , se realizaron en un AMD 4x 2.8 GHz de doble núcleo y un i7 de cuatro núcleos con hyperthreading (2.67 GHz).
Estos son los números:
i7
Especificaciones: Intel i7 2x quad-core @ 2.67 GHz Prueba: scala.threads.ParallelTests
Nombre de la prueba: loop_heap_read
Número de hilo: 1 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 9.0069 9.0036 9.0017 9.0084 9.0074 (avg = 9.1034 min = 8.9986 max = 21.0306)
Número de hilo: 2 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 4.5563 4.7128 4.5663 4.5617 4.5724 (avg = 4.6337 min = 4.5509 max = 13.9476)
Número de hilo: 4 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 2,3946 2,3979 2,3934 2,3937 2,3964 (promedio = 2,5113 mínimo = 2,3884 máximo = 13,5496)
Número de hilo: 8 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 2.4479 2.4362 2.4323 2.4472 2.4383 (avg = 2.5562 min = 2.4166 max = 10.3726)
Nombre de la prueba: threadlocal
Número de hilo: 1 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 91.1741 90.8978 90.6181 90.6200 90.6113 (avg = 91.0291 min = 90.6000 max = 129.7501)
Número de hilo: 2 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 45.3838 45.3858 45.6676 45.3772 45.3839 (prom = 46.0555 min = 45.3726 max = 90.7108)
Número de hilo: 4 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 22.8118 22.8135 59.1753 22.8229 22.8172 (prom = 23.9752 min = 22.7951 max = 59.1753)
Número de hilo: 8 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 22.2965 22.2415 22.3438 22.3109 22.4460 (prom = 23.2676 min = 22.2346 max = 50.3583)
AMD
Especificaciones: AMD 8220 4x dual-core @ 2.8 GHz Prueba: scala.threads.ParallelTests
Nombre de la prueba: loop_heap_read
Trabajo total: 20000000 Número de hilo: 1 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 12,625 12,631 12,634 12,632 12,628 (promedio = 12,7333 min = 12,619 max = 26,698)
Nombre de la prueba: loop_heap_read Trabajo total: 20000000
Tiempos de ejecución: (mostrando los últimos 5) 6.412 6.424 6.408 6.397 6.43 (promedio = 6.5367 mínimo = 6.393 máximo = 19.716)
Número de hilo: 4 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 3.385 4.298 9.7 6.535 3.385 (promedio = 5.6079 min = 3.354 max = 21.603)
Número de hilo: 8 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 5.389 5.795 10.818 3.823 3.824 (promedio = 5.5810 min = 2.405 max = 19.755)
Nombre de la prueba: threadlocal
Número de hilo: 1 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 200,217 207,335 200,241 207,342 200,23 (promedio = 202,2424 mínimo = 200,184 máximo = 245,369)
Número de hilo: 2 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 100.208 100.199 100.211 103.781 100.215 (avg = 102.2238 min = 100.192 max = 129.505)
Número de hilo: 4 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 62.101 67.629 62.087 52.021 55.766 (avg = 65.6361 min = 50.282 max = 167.433)
Número de hilo: 8 Pruebas totales: 200
Tiempos de ejecución: (mostrando los últimos 5) 40.672 74.301 34.434 41.549 28.119 (prom = 54.7701 min = 28.119 max = 94.424)
Resumen
Un subproceso local es aproximadamente 10-20 veces mayor que el del montón leído. También parece escalar bien en esta implementación de JVM y estas arquitecturas con la cantidad de procesadores.
fuente
"!"
nunca ocurre) en el primer método; el primer método es efectivamente equivalente a subclasificarThread
y darle un campo personalizado. El punto de referencia mide un caso de borde extremo en el que todo el cálculo consiste en leer una variable / subproceso local; es posible que las aplicaciones reales no se vean afectadas según su patrón de acceso, pero en el peor de los casos, se comportarán como se indicó anteriormente.Aquí va otra prueba. Los resultados muestran que ThreadLocal es un poco más lento que un campo normal, pero en el mismo orden. Aproximadamente un 12% más lento
public class Test { private static final int N = 100000000; private static int fieldExecTime = 0; private static int threadLocalExecTime = 0; public static void main(String[] args) throws InterruptedException { int execs = 10; for (int i = 0; i < execs; i++) { new FieldExample().run(i); new ThreadLocaldExample().run(i); } System.out.println("Field avg:"+(fieldExecTime / execs)); System.out.println("ThreadLocal avg:"+(threadLocalExecTime / execs)); } private static class FieldExample { private Map<String,String> map = new HashMap<String, String>(); public void run(int z) { System.out.println(z+"-Running field sample"); long start = System.currentTimeMillis(); for (int i = 0; i < N; i++){ String s = Integer.toString(i); map.put(s,"a"); map.remove(s); } long end = System.currentTimeMillis(); long t = (end - start); fieldExecTime += t; System.out.println(z+"-End field sample:"+t); } } private static class ThreadLocaldExample{ private ThreadLocal<Map<String,String>> myThreadLocal = new ThreadLocal<Map<String,String>>() { @Override protected Map<String, String> initialValue() { return new HashMap<String, String>(); } }; public void run(int z) { System.out.println(z+"-Running thread local sample"); long start = System.currentTimeMillis(); for (int i = 0; i < N; i++){ String s = Integer.toString(i); myThreadLocal.get().put(s, "a"); myThreadLocal.get().remove(s); } long end = System.currentTimeMillis(); long t = (end - start); threadLocalExecTime += t; System.out.println(z+"-End thread local sample:"+t); } } }'
Salida:
0-Muestra de campo en ejecución
Muestra de campo 0-End: 6044
0-Ejecución de muestra local de hilo
Muestra local de hilo 0-End: 6015
1-Muestra de campo en ejecución
Muestra de campo de 1 fin: 5095
1-Ejecución de muestra local de hilo
Muestra local de hilo de 1 extremo: 5720
Muestra de campo 2-Running
Muestra de campo de 2 extremos: 4842
Muestra local de 2 hilos en ejecución
Muestra local de hilo de 2 extremos: 5835
3-Muestra de campo en ejecución
Muestra de campo de 3 extremos: 4674
Muestra local de 3 hilos en ejecución
Muestra local de hilo de 3 extremos: 5287
Muestra de campo 4-Running
Muestra de campo de 4 extremos: 4849
Muestra local de 4 hilos en ejecución
Muestra local de hilo de 4 extremos: 5309
5-Muestra de campo en ejecución
Muestra de campo de 5 extremos: 4781
Muestra local de 5 hilos en ejecución
Muestra local de hilo de 5 extremos: 5330
6-Muestra de campo en ejecución
Muestra de campo de 6 extremos: 5294
Muestra local de 6 hilos en ejecución
Muestra local de hilo de 6 extremos: 5511
7-Ejecución de muestra de campo
Muestra de campo 7-End: 5119
Muestra local de 7 hilos en ejecución
Muestra local de hilo de 7 extremos: 5793
Muestra de campo de 8 ejecuciones
Muestra de campo de 8 extremos: 4977
Muestra local de 8 hilos en ejecución
Muestra local de hilo de 8 extremos: 6374
Muestra de campo 9-Running
Muestra de campo de 9 extremos: 4841
Muestra local de 9 hilos en ejecución
Muestra local de hilo de 9 extremos: 5471
Promedio de campo: 5051
Hilo Promedio local: 5664
Env:
versión de openjdk "1.8.0_131"
CPU Intel® Core ™ i7-7500U a 2,70 GHz × 4
Ubuntu 16.04 LTS
fuente
Int.toString)
cual es extremadamente costoso en comparación con lo que está probando. B) está haciendo dos operaciones de mapa en cada iteración, también totalmente sin relación y costosa. Intente incrementar un int primitivo de ThreadLocal en su lugar. C) Use enSystem.nanoTime
lugar deSystem.currentTimeMillis
, el primero es para crear perfiles, el segundo es para fines de fecha y hora del usuario y puede cambiar bajo sus pies. D) Debes evitar las asignaciones por completo, incluidas las de nivel superior para tus clases de "ejemplo"@Pete es la prueba correcta antes de optimizar.
Me sorprendería mucho si la construcción de un MessageDigest tiene una sobrecarga seria en comparación con su uso real.
Perder el uso de ThreadLocal puede ser una fuente de filtraciones y referencias colgantes, que no tienen un ciclo de vida claro, por lo general, nunca uso ThreadLocal sin un plan muy claro de cuándo se eliminará un recurso en particular.
fuente
Constrúyelo y mídelo.
Además, solo necesita un threadlocal si encapsula su comportamiento de digestión de mensajes en un objeto. Si necesita un MessageDigest local y un byte local [1000] para algún propósito, cree un objeto con un messageDigest y un campo byte [] y coloque ese objeto en ThreadLocal en lugar de ambos individualmente.
fuente