Domanda

Quanto viene letto dalla ThreadLocal variabile più lentamente rispetto al campo normale?

Più concretamente la creazione di oggetti semplici è più veloce o più lenta dell'accesso alla ThreadLocal<MessageDigest> variabile?

Suppongo che sia abbastanza veloce, quindi avere MessageDigest istanza è molto più veloce della creazione dell'istanza di <=> ogni volta. Ma questo vale anche per byte [10] o byte [1000] per esempio?

Modifica: la domanda è cosa sta realmente succedendo quando si chiama <=> get? Se questo è solo un campo, come un altro, la risposta sarebbe & Quot; è sempre la più veloce & Quot ;, giusto?

È stato utile?

Soluzione

Eseguendo benchmark non pubblicati, ThreadLocal.get richiede circa 35 cicli per iterazione sulla mia macchina. Non molto. Nell'implementazione di Sun una mappa hash di analisi lineare personalizzata in Thread mappe ThreadLocal s ai valori. Poiché è accessibile solo da un singolo thread, può essere molto veloce.

L'allocazione di piccoli oggetti richiede un numero simile di cicli, anche se a causa dell'esaurimento della cache è possibile ottenere cifre leggermente inferiori in un ciclo stretto.

La costruzione di MessageDigest è probabilmente relativamente costosa. Ha una discreta quantità di stato e la costruzione passa attraverso il meccanismo SPI. Potresti essere in grado di ottimizzare, ad esempio, clonando o fornendo il Provider.

Solo perché potrebbe essere più veloce memorizzare nella cache in <=> piuttosto che creare non significa necessariamente che le prestazioni del sistema aumenteranno. Avrai costi generali aggiuntivi relativi a GC che rallenta tutto.

A meno che l'applicazione non utilizzi molto <=>, potresti prendere in considerazione l'uso di una cache thread-safe convenzionale.

Altri suggerimenti

Nel 2009, alcune JVM hanno implementato ThreadLocal usando una HashMap non sincronizzata nell'oggetto Thread.currentThread (). Ciò lo ha reso estremamente veloce (anche se non altrettanto veloce di un normale accesso al campo, ovviamente), oltre a garantire che l'oggetto ThreadLocal sia stato riordinato quando il Thread è morto. Aggiornando questa risposta nel 2016, sembra che la maggior parte (tutte?) Le nuove JVM utilizzino una ThreadLocalMap con sondaggio lineare. Non sono sicuro delle prestazioni di quei & # 8211; ma non riesco a immaginare che sia significativamente peggio della precedente implementazione.

Ovviamente, anche il nuovo Object () è molto veloce in questi giorni, e anche i Garbage Collector sono molto bravi a recuperare oggetti di breve durata.

A meno che non si sia certi che la creazione di oggetti sia costosa o che sia necessario mantenere un certo stato su una base thread per thread, è meglio andare per l'allocazione più semplice quando è necessaria la soluzione e passare solo a ThreadLocal implementazione quando un profiler ti dice che è necessario.

Bella domanda, me lo sono chiesto di recente. Per darti numeri definiti, i benchmark di seguito (in Scala, compilati praticamente con gli stessi bytecode dell'equivalente codice Java):

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)
}

disponibile qui , sono stati eseguiti su un dual core AMD 4x da 2,8 GHz e un i7 quad-core con hyperthreading (2,67 GHz).

Questi sono i numeri:

i7

Specifiche: Intel i7 2x quad-core a 2,67 GHz Test: scala.threads.ParallelTests

Nome test: loop_heap_read

Numero discussione: 1 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  9.0069 9.0036 9.0017 9.0084 9.0074 (media = 9.1034 min = 8.9986 max = 21.0306)

Numero discussione: 2 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  4.5563 4.7128 4.5663 4.5617 4.5724 (media = 4.6337 min = 4.5509 max = 13.9476)

Numero discussione: 4 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  2.3946 2.3979 2.3934 2.3937 2.3964 (media = 2.5113 min = 2.3884 max = 13.5496)

Numero discussione: 8 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  2.4479 2.4362 2.4323 2.4472 2.4383 (media = 2.5562 min = 2.4166 max = 10.3726)

Nome test: threadlocal

Numero discussione: 1 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  91.1741 90.8978 90.6181 90.6200 90.6113 (media = 91.0291 min = 90.6000 max = 129.7501)

Numero discussione: 2 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  45.3838 45.3858 45.6676 45.3772 45.3839 (media = 46.0555 min = 45.3726 max = 90.7108)

Numero discussione: 4 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  22.8118 22.8135 59.1753 22.8229 22.8172 (media = 23.9752 min = 22.7951 max = 59.1753)

Numero discussione: 8 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  22.2965 22.2415 22.3438 22.3109 22.4460 (media = 23.2676 min = 22.2346 max = 50.3583)

AMD

Specifiche: AMD 8220 4x dual-core a 2,8 GHz Test: scala.threads.ParallelTests

Nome test: loop_heap_read

Totale lavoro: 20000000 Numero discussione: 1 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  12.625 12.631 12.634 12.632 12.628 (media = 12.7333 min = 12.619 max = 26.698)

Nome test: loop_heap_read Totale lavoro: 20000000

Tempi di esecuzione: (mostrando gli ultimi 5)  6.412 6.424 6.408 6.397 6.43 (media = 6.5367 min = 6.393 max = 19.716)

Numero discussione: 4 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  3.385 4.298 9.7 6.535 3.385 (media = 5.6079 min = 3.354 max = 21.603)

Numero discussione: 8 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  5.389 5.795 10.818 3.823 3.824 (media = 5.5810 min = 2.405 max = 19.755)

Nome test: threadlocal

Numero discussione: 1 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  200.217 207.335 200.241 207.342 200.23 (media = 202.2424 min = 200.184 max = 245.369)

Numero discussione: 2 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  100.208 100.199 100.211 103.781 100.215 (media = 102.2238 min = 100.192 max = 129.505)

Numero discussione: 4 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  62.101 67.629 62.087 52.021 55.766(media = 65.6361 min = 50.282 max = 167.433)

Numero discussione: 8 Test totali: 200

Tempi di esecuzione: (mostrando gli ultimi 5)  40.672 74.301 34.434 41.549 28.119 (media = 54.7701 min = 28.119 max = 94.424)

Sommario

Un thread locale è circa 10-20x quello dell'heap letto. Sembra inoltre ridimensionare bene su questa implementazione JVM e queste architetture con il numero di processori.

Qui va un altro test. I risultati mostrano che ThreadLocal è un po 'più lento di un campo normale, ma nello stesso ordine. Aprox 12% più 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);
    }
}
}'

Output:

Esempio di campo 0-Running

Esempio di campo 0-End: 6044

Esempio locale di thread in esecuzione 0

Esempio locale thread 0-End: 6015

Esempio di campo a 1 corsa

Esempio di campo a 1 estremità: 5095

Esempio locale di thread in esecuzione 1

Esempio locale di thread a 1 estremità: 5720

Esempio di campo a 2 corse

Esempio di campo a 2 estremità: 4842

Esempio locale di thread in esecuzione 2

Esempio locale di thread a 2 estremità: 5835

Esempio di campo a 3 corse

Esempio di campo a 3 estremità: 4674

Esempio locale di thread in esecuzione 3

Esempio locale di thread a 3 estremità: 5287

Esempio di campo a 4 corse

Esempio di campo a 4 estremità: 4849

Esempio locale di thread in esecuzione 4

Esempio locale di thread a 4 estremità: 5309

Esempio di 5 campi in esecuzione

Esempio di campo a 5 punte: 4781

Esempio locale di thread in esecuzione 5

Esempio locale di thread a 5 estremità: 5330

Esempio di 6 campi in esecuzione

Esempio di campo a 6 punte: 5294

Esempio locale di thread in esecuzione 6

Esempio locale di thread a 6 punte: 5511

Esempio di 7 campi in esecuzione

Esempio di campo a 7 punte: 5119

Esempio locale di thread in esecuzione 7

Esempio locale di thread a 7 estremità: 5793

Esempio di 8 campi in esecuzione

Esempio di campo a 8 punte: 4977

Esempio locale di thread in esecuzione 8

Esempio locale di thread a 8 estremità: 6374

Esempio di 9 campi in esecuzione

Esempio di campo a 9 punte: 4841

Esempio locale di thread in esecuzione 9

Esempio locale di thread a 9 punte: 5471

Field avg: 5051

Discussione media: 5664

Env:

versione openjdk " 1.8.0_131 "

Intel # 174 &; Nucleo # 8482 &; CPU i7-7500U a 2,70 GHz & # 215; 4

Ubuntu 16.04 LTS

@Pete è il test corretto prima di ottimizzare.

Sarei molto sorpreso se la costruzione di un MessageDigest comporta un grave sovraccarico rispetto all'utilizzo effettivo.

Perdere l'utilizzo di ThreadLocal può essere una fonte di perdite e riferimenti penzolanti, che non hanno un ciclo di vita chiaro, in genere non uso mai ThreadLocal senza un piano molto chiaro su quando una particolare risorsa verrà rimossa.

Costruiscilo e misuralo.

Inoltre, è necessario un solo threadlocal se si incapsula il comportamento di digestione dei messaggi in un oggetto. Se per qualche scopo hai bisogno di un MessageDigest locale e di un byte locale [1000], crea un oggetto con un campo messageDigest e un byte [] e inseriscilo in ThreadLocal anziché in entrambi singolarmente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top