Pregunta

¿Cuánto se lee de ThreadLocal variable más lento que del campo normal?

Más concretamente, ¿la creación de objetos simples es más rápida o lenta que el acceso a ThreadLocal<MessageDigest> variable?

Supongo que es lo suficientemente rápido como para que tener MessageDigest instancia sea mucho más rápido que crear una instancia de <=> cada vez. ¿Pero eso también se aplica al byte [10] o byte [1000] por ejemplo?

Editar: la pregunta es ¿qué sucede realmente al llamar a <=> 's get? Si eso es solo un campo, como cualquier otro, entonces la respuesta sería & Quot; siempre es más rápido & Quot ;, ¿verdad?

¿Fue útil?

Solución

Ejecutando puntos de referencia no publicados, ThreadLocal.get toma alrededor de 35 ciclos por iteración en mi máquina. No es un gran negocio. En la implementación de Sun, un mapa hash de sondeo lineal personalizado en Thread asigna ThreadLocal s a valores. Debido a que solo se accede por un solo hilo, puede ser muy rápido.

La asignación de objetos pequeños toma un número similar de ciclos, aunque debido al agotamiento de la memoria caché puede obtener cifras algo menores en un ciclo cerrado.

La construcción de MessageDigest es probable que sea relativamente costosa. Tiene una buena cantidad de estado y la construcción pasa por el mecanismo Provider SPI. Es posible que pueda optimizar, por ejemplo, clonando o proporcionando el <=>.

El hecho de que sea más rápido almacenar en caché un <=> en lugar de crear no significa necesariamente que el rendimiento del sistema aumentará. Tendrá gastos generales adicionales relacionados con GC que ralentizarán todo.

A menos que su aplicación utilice mucho <=> es posible que desee considerar el uso de un caché convencional seguro para subprocesos en su lugar.

Otros consejos

En 2009, algunas JVM implementaron ThreadLocal utilizando un HashMap no sincronizado en el objeto Thread.currentThread (). Esto lo hizo extremadamente rápido (aunque no casi tan rápido como usar un acceso de campo regular, por supuesto), así como también aseguró que el objeto ThreadLocal se arreglara cuando el Thread murió. Al actualizar esta respuesta en 2016, parece que la mayoría de las JVM más nuevas (¿todas?) Usan un ThreadLocalMap con sondeo lineal. No estoy seguro sobre el rendimiento de esos & # 8211; 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 va a ser costosa, o necesita persistir 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 un ThreadLocal implementación cuando un generador de perfiles le dice que necesita hacerlo.

Buena pregunta, me he estado preguntando eso recientemente. Para darle números definidos, los puntos de referencia a continuación (en Scala, compilados con prácticamente 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)
}

disponible aquí , se realizaron en un AMD 4x 2.8 GHz de doble núcleo y un i7 quad-core 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 prueba: loop_heap_read

Hilo num .: 1 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  9.0069 9.0036 9.0017 9.0084 9.0074 (promedio = 9.1034 min = 8.9986 max = 21.0306)

Hilo num .: 2 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  4.5563 4.7128 4.5663 4.5617 4.5724 (promedio = 4.6337 min = 4.5509 max = 13.9476)

Hilo num .: 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 min = 2.3884 max = 13.5496)

Hilo num .: 8 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  2.4479 2.4362 2.4323 2.4472 2.4383 (promedio = 2.5562 min = 2.4166 max = 10.3726)

Nombre de prueba: threadlocal

Hilo num .: 1 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  91.1741 90.8978 90.6181 90.6200 90.6113 (promedio = 91.0291 min = 90.6000 max = 129.7501)

Hilo num .: 2 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  45.3838 45.3858 45.6676 45.3772 45.3839 (promedio = 46.0555 min = 45.3726 max = 90.7108)

Hilo num .: 4 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  22.8118 22.8135 59.1753 22.8229 22.8172 (promedio = 23.9752 min = 22.7951 max = 59.1753)

Hilo num .: 8 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  22.2965 22.2415 22.3438 22.3109 22.4460 (promedio = 23.2676 min = 22.2346 max = 50.3583)

AMD

Especificaciones: AMD 8220 4x dual-core @ 2.8 GHz Prueba: scala.threads.ParallelTests

Nombre de prueba: loop_heap_read

Trabajo total: 20000000 Hilo num .: 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 máx = 26.698)

Nombre de 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 min = 6.393 max = 19.716)

Hilo num .: 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)

Hilo num .: 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 prueba: threadlocal

Hilo num .: 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 min = 200.184 max = 245.369)

Hilo num .: 2 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  100.208 100.199 100.211 103.781 100.215 (promedio = 102.2238 min = 100.192 max = 129.505)

Hilo num .: 4 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  62.101 67.629 62.087 52.021 55.766(promedio = 65.6361 min = 50.282 max = 167.433)

Hilo num .: 8 Pruebas totales: 200

Tiempos de ejecución: (mostrando los últimos 5)  40.672 74.301 34.434 41.549 28.119 (promedio = 54.7701 min = 28.119 max = 94.424)

Resumen

Un subproceso local es aproximadamente 10-20x el de la lectura del montón. También parece escalar bien en esta implementación de JVM y estas arquitecturas con la cantidad de procesadores.

Aquí va otra prueba. Los resultados muestran que ThreadLocal es un poco más lento que un campo normal, pero en el mismo orden. Aprox 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 subprocesos

Muestra local de subproceso de 0 extremos: 6015

1-Muestra de campo en ejecución

Muestra de campo 1-End: 5095

1-Ejecución de muestra local de subprocesos

Muestra local de subproceso de 1 extremo: 5720

Muestra de campo 2-Running

Muestra de campo de 2 extremos: 4842

Muestra local de subprocesos de 2 ejecuciones

Muestra local de subproceso de 2 extremos: 5835

Muestra de campo 3-Running

Muestra de campo de 3 extremos: 4674

Muestra local de subprocesos de 3 ejecuciones

Muestra local de subproceso 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 subprocesos de 4 extremos: 5309

Muestra de campo 5-Running

Muestra de campo de 5 extremos: 4781

Muestra local de subprocesos 5 en ejecución

Muestra local de subprocesos de 5 extremos: 5330

6-Muestra de campo en ejecución

Muestra de campo de 6 extremos: 5294

Muestra local de subprocesos de 6 ejecuciones

Muestra local de subproceso de 6 extremos: 5511

Muestra de campo 7-Running

Muestra de campo de 7 extremos: 5119

7-Ejecución de muestra local de hilo

Muestra local de subproceso de 7 extremos: 5793

Muestra de campo 8-Running

Muestra de campo de 8 extremos: 4977

Muestra local de 8 hilos en ejecución

Muestra local de subproceso de 8 extremos: 6374

9-Muestra de campo en ejecución

Muestra de campo de 9 extremos: 4841

Muestra local de 9 subprocesos en ejecución

Muestra local de subproceso de 9 extremos: 5471

Promedio de campo: 5051

Promedio ThreadLocal: 5664

Env:

versión openjdk " 1.8.0_131 "

Intel & # 174; Core & # 8482; CPU i7-7500U @ 2.70GHz & # 215; 4

Ubuntu 16.04 LTS

@Pete es la prueba correcta antes de optimizar.

Me sorprendería mucho si la construcción de un MessageDigest tiene una sobrecarga importante en comparación con su uso real.

Miss usando ThreadLocal puede ser una fuente de fugas y referencias colgantes, que no tienen un ciclo de vida claro, generalmente nunca uso ThreadLocal sin un plan muy claro de cuándo se eliminará un recurso en particular.

Construirlo y medirlo.

Además, solo necesita un hilo local 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 campo messageDigest y un byte [] y coloque ese objeto en ThreadLocal en lugar de ambos individualmente.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top