Dans quelle mesure la lecture d'une ThreadLocal
variable est-elle plus lente que celle d'un champ normal?
Plus concrètement, la création d'objet simple est-elle plus rapide ou plus lente que l'accès à la ThreadLocal
variable?
Je suppose qu'il est assez rapide pour que l' ThreadLocal<MessageDigest>
instance soit beaucoup plus rapide que la création d'une instance de MessageDigest
chaque fois. Mais cela vaut-il également pour 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
? Si ce n'est qu'un champ, comme n'importe quel autre, alors la réponse serait "c'est toujours le plus rapide", non?
Thread
s contient un hashmap (non synchronisé) où la clé est l'ThreadLocal
objet actuelRéponses:
L'exécution de benchmarks non publiés
ThreadLocal.get
prend environ 35 cycles par itération sur ma machine. Pas grand-chose. Dans l'implémentation de Sun, une mappe de hachage de sonde linéaire personnaliséeThread
mappeThreadLocal
les valeurs aux valeurs. Comme il n'est accessible que par un seul thread, il peut être très rapide.L'allocation de petits objets prend un nombre similaire de cycles, bien qu'en raison de l'épuisement du cache, vous puissiez obtenir des chiffres légèrement inférieurs dans une boucle serrée.
La construction de
MessageDigest
est susceptible d'être relativement coûteuse. Il a une bonne quantité d'état et la construction passe par leProvider
mécanisme SPI. Vous pourrez peut-être optimiser, par exemple, en clonant ou en fournissant le fichierProvider
.Ce n'est pas parce qu'il peut être plus rapide de mettre en cache dans un
ThreadLocal
plutôt que de créer que les performances du système augmenteront. Vous aurez des frais généraux supplémentaires liés au GC qui ralentissent tout.À moins que votre application ne l'utilise très fortement,
MessageDigest
vous pouvez envisager d'utiliser à la place un cache thread-safe conventionnel.la source
new org.bouncycastle.crypto.digests.SHA1Digest()
. Je suis sûr qu'aucun cache ne peut le battre.En 2009, certaines JVM ont implémenté ThreadLocal à l'aide d'un HashMap non synchronisé dans l'objet Thread.currentThread (). Cela le rendait extrêmement rapide (mais pas aussi rapide que l'utilisation d'un accès au champ normal, bien sûr), tout en garantissant que l'objet ThreadLocal soit rangé lorsque le Thread est mort. En mettant à jour cette réponse en 2016, il semble que la plupart des JVM les plus récentes utilisent un ThreadLocalMap avec un sondage linéaire. Je ne suis pas sûr de la performance de ceux-ci - mais je ne peux pas imaginer que ce soit bien pire que la mise en œuvre précédente.
Bien sûr, le nouvel Object () est également très rapide ces jours-ci, et les Garbage Collectors sont également très bons pour récupérer des objets de courte durée.
Sauf si vous êtes certain que la création d'objet va coûter cher, ou si vous devez conserver un état sur une base thread par thread, vous feriez mieux d'opter pour la solution d'allocation plus simple en cas de besoin, et de ne basculer vers une implémentation ThreadLocal que lorsqu'un profiler vous indique que vous devez.
la source
Bonne question, je me suis posé la question récemment. Pour vous donner des chiffres précis, les benchmarks ci-dessous (en Scala, compilés avec 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) }
disponibles ici , ont été réalisées sur un AMD 4x 2,8 GHz dual-core et un quad-core i7 avec hyperthreading (2,67 GHz).
Voici les chiffres:
i7
Spécifications: Intel i7 2x quad-core @ 2,67 GHz Test: scala.threads.ParallelTests
Nom du test: loop_heap_read
Numéro de la discussion: 1 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 9,0069 9,0036 9,0017 9,0084 9,0074 (moy = 9,1034 min = 8,9986 max = 21,0306)
Numéro de la discussion: 2 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 4,5563 4,7128 4,5663 4,5617 4,5724 (moy = 4,6337 min = 4,5509 max = 13,9476)
Numéro de la discussion: 4 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 2,3946 2,3979 2,3934 2,3937 2,3964 (moy = 2,5113 min = 2,3884 max = 13,5496)
Numéro de la discussion: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 2,4479 2,4362 2,4323 2,4472 2,4383 (moy = 2,5562 min = 2,4166 max = 10,3726)
Nom du test: threadlocal
Numéro de la discussion: 1 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 91,1741 90,8978 90,6181 90,6200 90,6113 (moy = 91,0291 min = 90,6000 max = 129,7501)
Numéro de la discussion: 2 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 45,3838 45,3858 45,6676 45,3772 45,3839 (moy = 46,0555 min = 45,3726 max = 90,7108)
Numéro de la discussion: 4 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 22.8118 22.8135 59.1753 22.8229 22.8172 (moy = 23.9752 min = 22.7951 max = 59.1753)
Numéro de la discussion: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 22,2965 22,2415 22,3438 22,3109 22,4460 (moy = 23,2676 min = 22,2346 max = 50,3583)
AMD
Spécifications: AMD 8220 4x dual-core @ 2.8 GHz Test: scala.threads.ParallelTests
Nom du test: loop_heap_read
Travail total: 20000000 N ° de la discussion: 1 Total des tests: 200
Temps de fonctionnement: (montrant les 5 derniers) 12,625 12,631 12,634 12,632 12,628 (moy = 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 (moy = 6,5367 min = 6,393 max = 19,716)
Numéro de la discussion: 4 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 3,385 4,298 9,7 6,535 3,385 (moy = 5,6079 min = 3,354 max = 21,603)
Numéro de la discussion: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 5,389 5,795 10,818 3,823 3,824 (moy = 5,5810 min = 2,405 max = 19,755)
Nom du test: threadlocal
Numéro de la discussion: 1 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 200,217 207,335 200,241 207,342 200,23 (moy = 202,2424 min = 200,184 max = 245,369)
Numéro de la discussion: 2 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 100,208 100,199 100,211 103,781 100,215 (moy = 102,2238 min = 100,192 max = 129,505)
Numéro de la discussion: 4 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 62,101 67,629 62,087 52,021 55,766 (moy = 65,6361 min = 50,282 max = 167,433)
Numéro de la discussion: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 40,672 74,301 34,434 41,549 28,119 (moy = 54,7701 min = 28,119 max = 94,424)
Sommaire
Un thread local est environ 10 à 20 fois celui de la lecture du tas. Il semble également bien évoluer sur cette implémentation JVM et ces architectures avec le nombre de processeurs.
la source
"!"
ne se produit jamais) dans la première méthode - la première méthode équivaut effectivement à sousThread
- classer et à lui donner un champ personnalisé. Le benchmark mesure un cas extrême où tout le calcul consiste à lire une variable / thread local - les applications réelles peuvent ne pas être affectées en fonction de leur modèle d'accès, mais dans le pire des cas, elles se comporteront comme ci-dessus.Ici, il va un autre test. Les résultats montrent que ThreadLocal est un peu plus lent qu'un champ normal, mais dans le même ordre. Environ 12% plus lent
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); } } }'
Production:
Échantillon de champ 0-Running
Échantillon de terrain 0-End: 6044
Exemple local de thread 0-Running
Échantillon local de thread 0-End: 6015
1-Échantillon de terrain en cours d'exécution
Échantillon de terrain à 1 extrémité: 5095
1-Exécution d'un exemple local de thread
Exemple local de thread à 1 extrémité: 5720
Échantillon de terrain à 2 courses
Échantillon de terrain à 2 extrémités: 4842
Exemple de thread local à 2 exécutions
Exemple local de thread à 2 extrémités: 5835
Échantillon de terrain à 3 courses
Échantillon de terrain à 3 extrémités: 4674
Exemple local de thread à 3 exécutions
Exemple local de thread à 3 extrémités: 5287
Échantillon de terrain 4-Running
Échantillon de terrain à 4 extrémités: 4849
Échantillon local de 4 threads en cours d'exécution
Exemple local de thread à 4 extrémités: 5309
Échantillon de terrain à 5 courses
Échantillon de terrain à 5 extrémités: 4781
Exemple local de thread à 5 exécutions
Exemple local de thread à 5 extrémités: 5330
6-Échantillon de terrain en cours d'exécution
Échantillon de terrain à 6 extrémités: 5294
6-Exécution d'un exemple local de thread
Échantillon local de thread à 6 extrémités: 5511
7-Échantillon de terrain en cours d'exécution
Échantillon de terrain à 7 extrémités: 5119
7-Exécution d'un exemple local de thread
Exemple local de thread à 7 extrémités: 5793
8-échantillon de terrain en cours d'exécution
Échantillon de terrain à 8 extrémités: 4977
Échantillon local de 8 threads en cours d'exécution
Exemple local de thread à 8 extrémités: 6374
9-Échantillon de terrain en cours d'exécution
Échantillon de terrain à 9 extrémités: 4841
9-Exécution d'un exemple local de thread
Exemple local de thread à 9 extrémités: 5471
Terrain moyen: 5051
ThreadMoyenne locale: 5664
Env:
version openjdk "1.8.0_131"
Processeur Intel® Core ™ i7-7500U à 2,70 GHz × 4
Ubuntu 16.04 LTS
la source
Int.toString)
ce qui est extrêmement cher par rapport à ce que vous testez. B) vous faites deux opérations de carte à chaque itération, également totalement indépendantes et coûteuses. Essayez plutôt d'incrémenter un int primitif à partir de ThreadLocal. C) Utilisez à laSystem.nanoTime
place deSystem.currentTimeMillis
, le premier est pour le profilage, le second est à des fins de date-heure de l'utilisateur et peut changer sous vos pieds. D) Vous devriez éviter complètement les allocs, y compris ceux de niveau supérieur pour vos classes "d'exemple"@Pete est un test correct avant d'optimiser.
Je serais très surpris si la construction d'un MessageDigest a une surcharge sérieuse par rapport à son utilisation active.
Manquer d'utiliser 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 du moment où une ressource particulière sera supprimée.
la source
Construisez-le et mesurez-le.
De plus, vous n'avez besoin que d'un threadlocal si vous encapsulez votre comportement de digestion de message dans un objet. Si vous avez besoin d'un MessageDigest local et d'un octet local [1000] dans un but précis, créez un objet avec un messageDigest et un champ byte [] et placez cet objet dans ThreadLocal plutôt que les deux individuellement.
la source