Pergunta

Quanto é lido da variável ThreadLocal mais lento do que de campo regular?

Mais concretamente é a criação de objetos simples mais rápido ou mais lento do que o acesso a variável ThreadLocal?

Eu suponho que é rápido o suficiente para que tendo instância ThreadLocal<MessageDigest> é muito mais rápido, em seguida, a criação de instância de MessageDigest cada vez. Mas isso também se aplica para byte [10] ou byte [1000], por exemplo?

Edit: A pergunta é o que realmente está acontecendo ao chamar get de ThreadLocal? Se isso é apenas um campo, como qualquer outro, então resposta seria "é sempre mais rápido", certo?

Foi útil?

Solução

Running benchmarks inéditos, ThreadLocal.get leva cerca de 35 ciclos por iteração na minha máquina. Não é um grande negócio. Na implementação da Sun um costume linear sondagem mapa hash em Thread mapeia ThreadLocals aos valores. Porque só é sempre acessado por um único segmento, pode ser muito rápido.

Alocação de pequenos objetos tomar um número similar de ciclos, embora por causa da exaustão de cache você pode obter valores um pouco mais baixos dentro de um loop.

Construção de MessageDigest é provável que seja relativamente caro. Ele tem uma boa quantidade de estado e construção atravessa o mecanismo Provider SPI. Você pode ser capaz de otimizar, por exemplo, a clonagem ou fornecendo o Provider.

Só porque ele pode ser mais rápido para o cache em um ThreadLocal em vez de criar não significa necessariamente que o desempenho do sistema irá aumentar. Você terá despesas adicionais relacionadas com GC que torna tudo mais lento.

A menos que sua aplicação muito fortemente usos MessageDigest você pode querer considerar o uso de um cache de thread-safe convencional vez.

Outras dicas

Em 2009, algumas JVMs implementado ThreadLocal usando um HashMap não sincronizadas no objeto Thread.currentThread (). Isso tornou extremamente rápido (embora não tão rápido quanto usando um acesso de campo regular, é claro), bem como assegurar que o objeto ThreadLocal ficou arrumado quando o Tópico morreu. Atualizando esta resposta em 2016, parece que a maioria (todos?) JVMs mais recentes usam um ThreadLocalMap com linear sondagem. Estou incerto sobre o desempenho daqueles - mas eu não posso imaginar que é significativamente pior do que a implementação anteriormente

.

Claro, new Object () também é muito rápido estes dias, e os coletores de lixo também são muito bons em recuperar objetos de vida curta.

A menos que você esteja certo de que a criação do objeto vai ser caro, ou você precisa de persistir algum estado em um segmento de base da linha, você é melhor fora de ir para o mais simples alocar quando necessário solução, e só mudar para um ThreadLocal implementação quando um profiler diz que você precisa.

Boa pergunta, eu estive me perguntando isso recentemente. Para lhe dar números definitivos, os valores de referência abaixo (em Scala, compilados a praticamente os mesmos bytecodes como o 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)
}

aqui , foram realizados em um AMD 4x 2.8 GHz dual-cores e um i7 quad-core 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

num Tópico .: 1 Total de Testes: 200

Run vezes: (mostrando última 5) 9,0069 9,0036 9,0017 9,0084 9,0074 (média = 9,1034 min = 8,9986 max = 21,0306)

num Tópico .: 2 Total de Testes: 200

Run vezes: (mostrando última 5) 4,5563 4,7128 4,5663 4,5617 4,5724 (média = 4,6337 min = 4,5509 max = 13,9476)

num Tópico .: 4 Total de Testes: 200

Run vezes: (mostrando última 5) 2,3946 2,3979 2,3934 2,3937 2,3964 (média = 2,5113 min = 2,3884 max = 13,5496)

num Tópico .: 8 Total de Testes: 200

Run vezes: (mostrando última 5) 2,4479 2,4362 2,4323 2,4472 2,4383 (média = 2,5562 min = 2,4166 max = 10,3726)

Nome do teste: ThreadLocal

num Tópico .: 1 Total de Testes: 200

Run vezes: (mostrando última 5) 91,1741 90,8978 90,6181 90,6200 90,6113 (média = 91,0291 90,6000 min = max = 129,7501)

num Tópico .: 2 Total de Testes: 200

Run vezes: (mostrando última 5) 45,3838 45,3858 45,6676 45,3772 45,3839 (média = 46,0555 45,3726 min = max = 90,7108)

num Tópico .: 4 Total de Testes: 200

Run vezes: (mostrando última 5) 22,8118 22,8135 59,1753 22,8229 22,8172 (média = 23,9752 22,7951 min = max = 59,1753)

num Tópico .: 8 Total de Testes: 200

Run vezes: (mostrando última 5) 22,2965 22,2415 22,3438 22,3109 22,4460 (média = 23,2676 22,2346 min = max = 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 num fio .: 1 Total de Testes: 200

Run vezes: (mostrando última 5) 12,625 12,631 12,634 12,632 12,628 (média = 12,7333 12,619 min = max = 26,698)

Nome do teste: loop_heap_read trabalho total: 20000000

Run vezes: (mostrando última 5) 6,412 6,424 6,408 6,397 6,43 (média = 6,5367 min = 6,393 max = 19,716)

num Tópico .: 4 Total de Testes: 200

Run vezes: (mostrando última 5) 3,385 4,298 9,7 6,535 3,385 (média = 5,6079 min = 3,354 max = 21,603)

num Tópico .: 8 Total de Testes: 200

Run vezes: (mostrando última 5) 5,389 5,795 10,818 3,823 3,824 (média = 5,5810 min = 2,405 max = 19,755)

Nome do teste: ThreadLocal

num Tópico .: 1 Total de Testes: 200

Run vezes: (mostrando última 5) 200,217 207,335 200,241 207,342 200,23 (média = 202,2424 min = 200,184 max = 245,369)

num Tópico .: 2 Total de Testes: 200

Run vezes: (mostrando última 5) 100,208 100,199 100,211 103,781 100,215 (média = 102,2238 min = 100,192 max = 129,505)

num Tópico .: 4 Total de Testes: 200

Run vezes: (mostrando última 5) 62,101 67,629 62,087 52,02155,766 (média = 65,6361 50,282 min = max = 167,433)

num Tópico .: 8 Total de Testes: 200

Run vezes: (mostrando última 5) 40,672 74,301 34,434 41,549 28,119 (média = 54,7701 28,119 min = max = 94,424)

Resumo

A thread local é de cerca de 10-20x que da pilha ler. Parece também para dimensionar bem nesta implementação JVM e essas arquiteturas com o número de processadores.

Aqui vai mais um 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);
    }
}
}'

Output:

0-Running amostra de campo

Exemplo de campo 0-End: 6044

0-Running fio amostra locais

0-End fio amostra locais: 6015

1-Running amostra de campo

Exemplo de campo de 1 End: 5095

1-Running fio amostra locais

1-End fio amostra locais: 5720

2-Running amostra de campo

Exemplo de campo de 2 End: 4842

2-Running fio amostra locais

2-End fio amostra locais: 5835

3-Running amostra de campo

Exemplo de campo de 3 Fim: 4674

3-Running fio amostra locais

3-End fio amostra locais: 5287

4-Running amostra de campo

Exemplo de campo 4-End: 4849

4-Running fio amostra locais

4-End fio amostra locais: 5309

5-Running amostra de campo

Exemplo de campo 5-End: 4781

5-Running fio amostra locais

5-End fio amostra locais: 5330

6-Running amostra de campo

Exemplo de campo 6-End: 5294

6-Running fio amostra locais

6-End fio amostra locais: 5511

7-Running amostra de campo

Exemplo de campo 7-End: 5119

7-Running fio amostra locais

7-End fio amostra locais: 5793

8-Running amostra de campo

Exemplo de campo de 8 End: 4977

8-Running fio amostra locais

8-End fio amostra locais: 6374

9-Running amostra de campo

Exemplo de campo de 9-End: 4841

9-Running fio amostra locais

9-End fio amostra locais: 5471

O campo avg: 5051

ThreadLocal avg: 5664

Env:

Versão openjdk "1.8.0_131"

Intel® Core ™ i7-7500U CPU @ 2.70GHz × 4

Ubuntu 16.04 LTS

@Pete é teste correto antes de você otimizar.

Eu ficaria muito surpreso se a construção de um MessageDigest tem qualquer sobrecarga grave quando comparado com actaully usá-lo.

Miss usando ThreadLocal pode ser uma fonte de vazamentos e referências pendentes, que não têm um ciclo de vida clara, geralmente eu nunca usam ThreadLocal sem um plano muito clara de quando um determinado recurso será removido.

Construí-lo e medi-lo.

Além disso, você só precisa de um ThreadLocal se você encapsular sua mensagem comportamento digerir em um objeto. Se você precisa de um MessageDigest local e um byte locais [1000] para algum propósito, criar um objeto com um MessageDigest e um campo de byte [] e colocar esse objeto na ThreadLocal em vez de individualmente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top