88 votes

Performance de la variable ThreadLocal

Combien est lu de ThreadLocal variable plus lente que celle du champ normal ?

Plus concrètement, la simple création d'un objet est-elle plus rapide ou plus lente que l'accès à l'information ? ThreadLocal variable ?

Je suppose qu'il est assez rapide pour que le fait d'avoir ThreadLocal<MessageDigest> est beaucoup plus rapide que la création d'une instance de MessageDigest à chaque fois. Mais cela s'applique-t-il également à l'octet[10] ou à l'octet[1000], par exemple ?

Edit : La question est de savoir ce qui se passe réellement lors de l'appel. ThreadLocal a obtenu ? Si c'est juste un champ, comme n'importe quel autre, alors la réponse serait "c'est toujours le plus rapide", non ?

2 votes

Un thread local est fondamentalement un champ contenant un hashmap et un lookup où la clé est l'objet thread actuel. Il est donc beaucoup plus lent mais toujours rapide :)

1 votes

@eckes : il se comporte certainement comme ça, mais il n'est généralement pas implémenté de cette façon. Au lieu de cela, Thread contiennent une table de hachage (non synchronisée) dont la clé est l'adresse courante de l'utilisateur. ThreadLocal objet

57voto

Bill Michell Points 4879

En 2009, certaines JVM ont implémenté ThreadLocal en utilisant une méthode non synchronisée HashMap dans le Thread.currentThread() objet. Cela permet d'être extrêmement rapide (même si ce n'est pas aussi rapide que d'utiliser un accès normal aux champs, bien sûr), et de garantir que l'objet ThreadLocal a été mis en ordre lorsque l'objet Thread est mort. En mettant à jour cette réponse en 2016, il semble que la plupart (toutes ?) des JVM les plus récentes utilisent une ThreadLocalMap avec un sondage linéaire. Je ne suis pas certain des performances de ces dernières, mais je ne peux pas imaginer qu'elles soient nettement inférieures à celles de l'implémentation précédente.

Bien sûr, new Object() est également très rapide de nos jours, et les collecteurs de déchets sont également très bons pour récupérer les objets éphémères.

À moins que vous ne soyez certain que la création d'un objet va être coûteuse, ou que vous ayez besoin de faire persister un état sur une base thread par thread, il est préférable d'opter pour la solution plus simple d'allocation quand c'est nécessaire, et de ne basculer vers un objet de type ThreadLocal quand un profileur vous dit que vous devez le faire.

4 votes

+1 pour être la seule réponse à répondre réellement à la question.

0 votes

Pouvez-vous me donner un exemple d'une JVM moderne qui n'utilise pas le sondage linéaire pour ThreadLocalMap ? Java 8 OpenJDK semble toujours utiliser ThreadLocalMap avec un sondage linéaire. grepcode.com/file/repository.grepcode.com/java/Root/jdk/openjdk/

1 votes

@Karthick Désolé, non je ne peux pas. J'ai écrit ceci en 2009. Je vais mettre à jour.

41voto

Tom Hawtin - tackline Points 82671

Exécution de benchmarks non publiés, ThreadLocal.get prend environ 35 cycles par itération sur ma machine. Ce n'est pas énorme. Dans l'implémentation de Sun, une carte de hachage de sondage linéaire personnalisée dans le fichier Thread cartes ThreadLocal aux valeurs. Comme il n'est accédé que par un seul thread, il peut être très rapide.

L'allocation de petits objets prend un nombre de cycles similaire, bien qu'en raison de l'épuisement du cache, vous puissiez obtenir des chiffres légèrement inférieurs dans une boucle serrée.

Construction de MessageDigest est susceptible d'être relativement coûteux. Il y a une bonne quantité d'État et la construction passe par l'État. Provider Mécanisme SPI. Il est possible de l'optimiser, par exemple, en clonant ou en fournissant le mécanisme de l Provider .

Ce n'est pas parce qu'il peut être plus rapide de mettre en cache dans une ThreadLocal plutôt que de créer ne signifie pas nécessairement que les performances du système vont augmenter. Vous aurez des frais généraux supplémentaires liés à la GC qui ralentit tout.

À moins que votre application n'utilise très fortement MessageDigest vous pourriez envisager d'utiliser un cache conventionnel à sécurité thread à la place.

5 votes

IMHO, le moyen le plus rapide est d'ignorer le SPI et d'utiliser quelque chose comme new org.bouncycastle.crypto.digests.SHA1Digest() . Je suis sûr qu'aucun cache ne peut le battre.

35voto

axel22 Points 17400

Bonne question, je me la suis posée récemment. Pour vous donner des chiffres précis, les benchmarks ci-dessous (en Scala, compilés vers pratiquement les mêmes bytecodes que le code Java équivalent) :

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 sur aquí ont été réalisées sur un AMD 4x 2,8 GHz dual-cores et un i7 quad-core avec hyperthreading (2,67 GHz).

Voici les chiffres :

i7

Des lunettes : Intel i7 2x quad-core @ 2.67 GHz Test : scala.threads.ParallelTests

Nom du test : loop_heap_read

Nombre de fils : 1 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 9.0069 9.0036 9.0017 9.0084 9.0074 (moyenne = 9.1034 min = 8.9986 max = 21.0306 )

Nombre de fils : 2 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 4.5563 4.7128 4.5663 4.5617 4.5724 (moyenne = 4.6337 min = 4.5509 max = 13.9476 )

Nombre de fils : 4 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 2.3946 2.3979 2.3934 2.3937 2.3964 (moyenne = 2.5113 min = 2.3884 max = 13.5496 )

Nombre de fils : 8 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 2.4479 2.4362 2.4323 2.4472 2.4383 (moyenne = 2.5562 min = 2.4166 max = 10.3726 )

Nom du test : threadlocal

Nombre de fils : 1 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 91.1741 90.8978 90.6181 90.6200 90.6113 (moyenne = 91.0291 min = 90.6000 max = 129.7501 )

Nombre de fils : 2 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 45.3838 45.3858 45.6676 45.3772 45.3839 (moyenne = 46.0555 min = 45.3726 max = 90.7108 )

Nombre de fils : 4 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 22.8118 22.8135 59.1753 22.8229 22.8172 (moyenne = 23.9752 min = 22.7951 max = 59.1753 )

Nombre de fils : 8 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 22.2965 22.2415 22.3438 22.3109 22.4460 (moyenne = 23.2676 min = 22.2346 max = 50.3583 )

AMD

Specs : AMD 8220 4x dual-core @ 2.8 GHz Test : scala.threads.ParallelTests

Nom du test : loop_heap_read

Travail total : 20000000 Nombre de fils : 1 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 12.625 12.631 12.634 12.632 12.628 (moyenne = 12.7333 min = 12.619 max = 26.698 )

Nom du test : loop_heap_read Travail total : 20000000

Temps d'exécution : (montrant les 5 derniers) 6.412 6.424 6.408 6.397 6.43 (moyenne = 6.5367 min = 6.393 max = 19.716 )

Nombre de fils : 4 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 3.385 4.298 9.7 6.535 3.385 (moyenne = 5.6079 min = 3.354 max = 21.603 )

Nombre de fils : 8 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 5.389 5.795 10.818 3.823 3.824 (moyenne = 5.5810 min = 2.405 max = 19.755 )

Nom du test : threadlocal

Nombre de fils : 1 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 200.217 207.335 200.241 207.342 200.23 (moyenne = 202.2424 min = 200.184 max = 245.369 )

Nombre de fils : 2 Total des tests : 200

Temps d'exécution : (affichant les 5 derniers) 100.208 100.199 100.211 103.781 100.215 (moyenne = 102.2238 min = 100.192 max = 129.505 )

Nombre de fils : 4 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 62.101 67.629 62.087 52.021 55.766 (moyenne = 65.6361 min = 50.282 max = 167.433 )

Nombre de fils : 8 Total des tests : 200

Temps d'exécution : (montrant les 5 derniers) 40.672 74.301 34.434 41.549 28.119 (moyenne = 54.7701 min = 28.119 max = 94.424 )

Résumé

Un thread local est environ 10-20x celui de la lecture du tas. Il semble également bien s'adapter à cette implémentation JVM et à ces architectures avec le nombre de processeurs.

5 votes

+1 Félicitations pour être le seul à donner des résultats quantitatifs. Je suis un peu sceptique parce que ces tests sont en Scala, mais comme vous l'avez dit, les bytecodes Java devraient être similaires...

0 votes

Merci ! Cette boucle while produit pratiquement le même bytecode que celui produit par le code Java correspondant. Des temps différents peuvent être observés sur différentes VM, cependant - ceci a été testé sur une Sun JVM1.6.

0 votes

Ce code de référence ne simule pas un bon cas d'utilisation de ThreadLocal. Dans la première méthode : chaque thread aura une représentation partagée en mémoire, la chaîne ne change pas. Dans la deuxième méthode, vous évaluez le coût d'une recherche dans une table de hachage où la chaîne est disjonctive entre tous les threads.

5voto

ReneS Points 1526

Hors sujet : ThreadLocal a tendance à être un problème de mémoire dans les applications serveur où les threads vivent éternellement en tant que worker threads. Vous pourriez empiler des données sans même le savoir.

3voto

Gareth Davis Points 16190

@Pete a raison de tester avant d'optimiser.

Je serais très surpris que la construction d'un MessageDigest entraîne une surcharge importante par rapport à son utilisation effective.

Mlle utilisant ThreadLocal peut être une source de fuites et de références pendantes, qui n'ont pas un cycle de vie clair, généralement je n'utilise jamais ThreadLocal sans un plan très clair de quand une ressource particulière sera retirée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X