Quanto é lido da ThreadLocal
variável mais lentamente do que do campo regular?
Mais concretamente, a criação de um objeto simples é mais rápida ou mais lenta do que o acesso à ThreadLocal
variável?
Presumo que seja rápido o suficiente para que ter uma ThreadLocal<MessageDigest>
instância seja muito mais rápido do que criar uma instância de MessageDigest
cada vez. Mas isso também se aplica a byte [10] ou byte [1000], por exemplo?
Edit: A pergunta é o que realmente está acontecendo quando a chamada ThreadLocal
é obtida? Se fosse apenas um campo, como qualquer outro, a resposta seria "é sempre mais rápido", certo?
Thread
s contêm um hashmap (não sincronizado) em que a chave é oThreadLocal
objeto atualRespostas:
A execução de benchmarks não publicados
ThreadLocal.get
leva cerca de 35 ciclos por iteração na minha máquina. Não muito. Na implementação da Sun, um mapa hash de teste linear personalizado emThread
mapasThreadLocal
de valores. Como só é acessado por um único thread, pode ser muito rápido.A alocação de pequenos objetos leva um número semelhante de ciclos, embora, devido ao esgotamento do cache, você possa obter números um pouco mais baixos em um loop fechado.
A construção de
MessageDigest
provavelmente será relativamente cara. Tem um bom estado de conservação e a construção passa peloProvider
mecanismo do SPI. Você pode ser capaz de otimizar, por exemplo, clonando ou fornecendo oProvider
.Só porque pode ser mais rápido armazenar em cache em um
ThreadLocal
do que criar, não significa necessariamente que o desempenho do sistema aumentará. Você terá overheads adicionais relacionados ao GC, o que torna tudo mais lento.A menos que seu aplicativo use muito,
MessageDigest
você pode querer considerar o uso de um cache seguro de thread convencional.fonte
new org.bouncycastle.crypto.digests.SHA1Digest()
. Tenho certeza de que nenhum cache pode vencê-lo.Em 2009, algumas JVMs implementaram ThreadLocal usando um HashMap não sincronizado no objeto Thread.currentThread (). Isso o tornou extremamente rápido (embora não tão rápido quanto usar um acesso de campo regular, é claro), bem como garantiu que o objeto ThreadLocal fosse organizado quando o Thread morresse. Atualizando esta resposta em 2016, parece que a maioria (todas?) JVMs mais recentes usam um ThreadLocalMap com sondagem linear. Não tenho certeza sobre o desempenho deles - mas não posso imaginar que seja significativamente pior do que a implementação anterior.
Obviamente, new Object () também é muito rápido atualmente, e os Garbage Collectors também são muito bons em recuperar objetos de vida curta.
A menos que você tenha certeza de que a criação de objeto vai ser cara, ou você precisa persistir algum estado em uma base thread a thread, é melhor você ir para a solução mais simples de alocação quando necessário, e apenas alternar para uma implementação ThreadLocal quando um o profiler diz que você precisa.
fonte
Boa pergunta, tenho me perguntado isso recentemente. Para lhe dar números definitivos, os benchmarks abaixo (em Scala, compilados virtualmente com os mesmos bytecodes do 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) }
disponíveis aqui , foram realizados em um AMD 4x 2,8 GHz dual-core e um quad-core i7 com hyperthreading (2,67 GHz).
Estes são os números:
i7
Especificações: Intel i7 2x quad-core @ 2,67 GHz Teste: scala.threads.ParallelTests
Nome do teste: loop_heap_read
Número da linha: 1 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 9,0069 9,0036 9,0017 9,0084 9,0074 (média = 9,1034 min = 8,9986 máx = 21,0306)
Número da linha: 2 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 4,5563 4,7128 4,5663 4,5617 4,5724 (média = 4,6337 min = 4,5509 máx = 13,9476)
Número da linha: 4 Total de testes: 200
Tempos de execução: (mostrando os 5 últimos) 2,3946 2,3979 2,3934 2,3937 2,3964 (média = 2,5113 min = 2,3884 máx = 13,5496)
Número da linha: 8 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 2,4479 2,4362 2,4323 2,4472 2,4383 (média = 2,5562 min = 2,4166 máx = 10,3726)
Nome do teste: threadlocal
Número da linha: 1 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 91,1741 90,8978 90,6181 90,6200 90,6113 (média = 91,0291 min = 90,6000 max = 129,7501)
Número da linha: 2 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 45,3838 45,3858 45,6676 45,3772 45,3839 (média = 46,0555 min = 45,3726 máx = 90,7108)
Número da linha: 4 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 22,8118 22,8135 59,1753 22,8229 22,8172 (média = 23,9752 min = 22,7951 max = 59,1753)
Número da linha: 8 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 22,2965 22,2415 22,3438 22,3109 22,4460 (média = 23,2676 min = 22,2346 máx = 50,3583)
AMD
Especificações: AMD 8220 4x dual-core @ 2,8 GHz Teste: scala.threads.ParallelTests
Nome do teste: loop_heap_read
Trabalho total: 20000000 Número da linha: 1 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 12,625 12,631 12,634 12,632 12,628 (média = 12,7333 min = 12,619 máx = 26,698)
Nome do teste: loop_heap_read Trabalho total: 20000000
Tempos de execução: (mostrando os últimos 5) 6,412 6,424 6,408 6,397 6,43 (média = 6,5367 min = 6,393 máx = 19,716)
Número da linha: 4 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 3,385 4,298 9,7 6,535 3,385 (média = 5,6079 min = 3,354 máx = 21,603)
Número da linha: 8 Total de testes: 200
Tempos de execução: (mostrando os 5 últimos) 5,389 5,795 10,818 3,823 3,824 (média = 5,5810 min = 2,405 máx = 19,755)
Nome do teste: threadlocal
Número da linha: 1 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 200,217 207,335 200,241 207,342 200,23 (média = 202,2424 min = 200,184 máx = 245,369)
Número da linha: 2 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 100,208 100,199 100,211 103,781 100,215 (média = 102,2238 min = 100,192 máx = 129,505)
Número da linha: 4 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 62,101 67,629 62,087 52,021 55,766 (média = 65,6361 min = 50,282 máx = 167,433)
Número da linha: 8 Total de testes: 200
Tempos de execução: (mostrando os últimos 5) 40,672 74,301 34,434 41,549 28,119 (média = 54,7701 min = 28,119 máx = 94,424)
Resumo
Um thread local é cerca de 10 a 20 vezes maior que a leitura do heap. Ele também parece escalar bem nesta implementação de JVM e nessas arquiteturas com o número de processadores.
fonte
"!"
nunca ocorre) no primeiro método - o primeiro método é efetivamente equivalente a subclassificaçãoThread
e atribuição de um campo personalizado. O benchmark mede um caso extremo onde todo o cálculo consiste na leitura de uma variável / thread local - aplicativos reais podem não ser afetados dependendo de seu padrão de acesso, mas no pior caso, eles se comportarão como acima.Aqui vai outro teste. Os resultados mostram que ThreadLocal é um pouco mais lento do que um campo regular, mas na mesma ordem. Aprox 12% mais 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); } } }'
Resultado:
0-Amostra de campo de corrida
Amostra de campo 0-End: 6044
0-Running thread local sample
Amostra local de segmento 0-End: 6015
1-Amostra de campo de corrida
Amostra de campo de 1 extremidade: 5095
1 - Amostra local de thread em execução
Amostra local de thread de 1 extremidade: 5720
2-Amostra de campo de corrida
Amostra de campo de 2 extremidades: 4842
Amostra local de thread 2 em execução
Amostra local de 2 pontas: 5835
3-Amostra de campo de corrida
Amostra de campo de 3 extremidades: 4674
3-Running thread local sample
Amostra local de thread de 3 pontas: 5287
4-Amostra de campo de corrida
Amostra de campo de 4 extremidades: 4849
4-Executando amostra local de thread
Amostra local de 4 pontas: 5309
5-Amostra de campo de corrida
Amostra de campo de 5 extremidades: 4781
5-Amostra local de thread em execução
Amostra local de encadeamento de 5 extremidades: 5330
6-Amostra de campo de corrida
Amostra de campo 6-final: 5294
6-Running thread local sample
Amostra local do segmento 6-End: 5511
7-Amostra de campo de corrida
Amostra de campo de 7 pontas: 5119
7-Running thread local sample
Amostra local de thread 7-End: 5793
8-Amostra de campo de corrida
Amostra de campo de 8 pontas: 4977
8-Running thread local sample
Amostra local de segmento de 8 pontas: 6374
9-Amostra de campo de corrida
Amostra de campo 9-final: 4841
9-Running thread local sample
Amostra local de encadeamento de 9 extremidades: 5471
Média de campo: 5051
ThreadLocal avg: 5664
Env:
versão openjdk "1.8.0_131"
CPU Intel® Core ™ i7-7500U @ 2,70 GHz × 4
Ubuntu 16.04 LTS
fonte
Int.toString)
que é extremamente caro em comparação com o que você está testando. B) Você está fazendo duas operações de mapa a cada iteração, também totalmente não relacionadas e caras. Em vez disso, tente incrementar um int primitivo de ThreadLocal. C) Use emSystem.nanoTime
vez deSystem.currentTimeMillis
, o primeiro é para criação de perfil, o último é para fins de data e hora do usuário e pode mudar sob seus pés. D) Você deve evitar alocações totalmente, incluindo as de nível superior para suas classes "exemplo"@Pete é o teste correto antes de otimizar.
Eu ficaria muito surpreso se construir um MessageDigest tivesse alguma sobrecarga séria em comparação com usá-lo de forma ativa.
A falta de uso do ThreadLocal pode ser uma fonte de vazamentos e referências pendentes, que não têm um ciclo de vida claro, geralmente eu nunca uso o ThreadLocal sem um plano muito claro de quando um determinado recurso será removido.
fonte
Construa e meça.
Além disso, você só precisa de um threadlocal se encapsular seu comportamento de compilação de mensagem em um objeto. Se você precisar de um MessageDigest local e um byte local [1000] para algum propósito, crie um objeto com um messageDigest e um campo byte [] e coloque esse objeto no ThreadLocal em vez de ambos individualmente.
fonte