Performance de la variable ThreadLocal
-
03-07-2019 - |
Question
Quel est le volume lu de la ThreadLocal
variable plus lente que du champ normal?
Plus concrètement, la création d'objet simple est-elle plus rapide ou plus lente que l'accès à la ThreadLocal<MessageDigest>
variable?
Je suppose que c'est assez rapide pour que l'instance MessageDigest
soit beaucoup plus rapide que la création d'une instance de <=> à chaque fois. Mais est-ce que cela s'applique aussi à byte [10] ou byte [1000] par exemple?
Modifier: La question est de savoir ce qui se passe réellement lorsque vous appelez <=>? S'il ne s'agit que d'un champ, comme d'un autre, la réponse serait & "C'est toujours le plus rapide &"; Non?
La solution
L'exécution de tests de performance non publiés, ThreadLocal.get
nécessite environ 35 cycles par itération sur ma machine. Pas beaucoup. Dans l'implémentation de Sun, une carte de hachage de sondage linéaire personnalisée entre Thread
mappe ThreadLocal
s à des valeurs. Comme il n’est accessible que par un seul thread, cela peut être très rapide.
L'attribution de petits objets prend un nombre de cycles similaire. Toutefois, en raison de l'épuisement du cache, il est possible que les chiffres soient légèrement inférieurs dans une boucle serrée.
La construction de MessageDigest
sera probablement relativement coûteuse. Il a pas mal d'état et la construction passe par le mécanisme Provider
SPI. Vous pourrez peut-être optimiser, par exemple, en clonant ou en fournissant le <=>.
Ce n’est peut-être pas simplement parce que la mise en cache de <=> la création est plus rapide que la création que cela signifie que les performances du système vont augmenter. Vous aurez des frais généraux supplémentaires liés à GC qui ralentissent tout.
À moins que votre application utilise très fréquemment <=>, vous pouvez envisager d'utiliser plutôt un cache thread-safe conventionnel.
Autres conseils
En 2009, certaines machines virtuelles Java ont implémenté ThreadLocal à l'aide d'un HashMap non synchronisé dans l'objet Thread.currentThread (). Cela le rendait extrêmement rapide (bien que pas aussi rapide que d'utiliser un accès de champ régulier, bien sûr), ainsi que de s'assurer que l'objet ThreadLocal était bien rangé à la mort du Thread. En mettant à jour cette réponse en 2016, il semble que la plupart (toutes?) Des nouvelles machines virtuelles utilisent une ThreadLocalMap avec une analyse linéaire. Je ne suis pas sûr de la performance de ces & # 8211; mais je ne peux pas imaginer que cela est bien pire que la mise en œuvre antérieure.
Bien entendu, nouvel objet () est également très rapide de nos jours, et les récupérateurs d'ordures sont également très efficaces pour récupérer des objets éphémères.
Sauf si vous êtes certain que la création d'objet coûtera cher ou si vous devez conserver certains états thread par thread, il vaut mieux opter pour la solution d'allocation simple lorsque nécessaire, et pour basculer uniquement vers un ThreadLocal. mise en œuvre lorsqu'un profileur vous dit que vous en avez besoin.
Bonne question, je me suis posé la question récemment. Pour vous donner des chiffres précis, utilisez les repères ci-dessous (dans Scala, compilés pour créer pratiquement les mêmes octets 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 ici , ont été réalisés sur un processeur double cœur AMD 4x de 2,8 GHz et un processeur i7 à quatre coeurs avec hyperthreading (2,67 GHz).
Ce sont les chiffres:
i7
Spécifications: Intel i7 2x quad-core @ 2,67 GHz Testez: scala.threads.TarallelTests
Nom du test: loop_heap_read
Nombre de fils: 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)
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 (avg = 2.5113 min = 2.3884 max = 13.5496)
Nombre de fils: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 2,44479 2,4362 2,4323 2,44472 2,4383 (avg = 2,5562 min = 2,4166 max = 10,3726)
Nom du test: threadlocal
Nombre de fils: 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)
Nombre de fils: 2 Total des tests: 200
Temps d'exécution: (montrant 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: (montrant les 5 derniers) 22.8118 22.8135 59.1753 22.8229 22.8172 (avg = 23.9752 min = 22.7951 max = 59.1753)
Nombre de fils: 8 Total des tests: 200
Temps d'exécution: (montrant les 5 derniers) 22.2965 22.2415 22.3438 22.3109 22.4460 (avg = 23.2676 min = 22.2346 max = 50.3583)
DMLA
Spécifications: AMD 8220 4x double cœur à 2,8 GHz Testez: scala.threads.TarallelTests
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 (avg = 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)
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: (montrant les 5 derniers) 200.217 207.335 200.241 207.342 200.23 (moy = 202.2424 min = 200.184 max = 245.369)
Nombre de fils: 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)
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(avg = 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 (avg = 54.7701 min = 28.119 max = 94.424)
Résumé
Un thread local correspond à environ 10 à 20 fois celui de la lecture du tas. Il semble également bien s’adapter à cette implémentation JVM et à ces architectures avec le nombre de processeurs.
Voilà 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);
}
}
}'
Sortie:
0-Échantillon de terrain en cours d'exécution
Échantillon de champ 0-End: 6044
Exemple local de thread en cours d'exécution
Échantillon local de thread 0-End: 6015
1-Échantillon de terrain en cours
Échantillon de champ 1-End: 5095
Échantillon local d'un thread en cours d'exécution
Échantillon local de thread 1-End: 5720
Échantillon de terrain en cours d'exécution
Échantillon de champ 2-End: 4842
Exemple local de thread en cours d'exécution
Échantillon local de thread 2-End: 5835
Échantillon de terrain en cours d'exécution
Échantillon de champ à 3 extrémités: 4674
Exemple local de thread en cours d'exécution
Échantillon local d'unité d'exécution à 3 extrémités: 5287
Échantillon de terrain en cours d'exécution
Échantillon de champ 4-End: 4849
Exemple local de thread en cours d'exécution
Échantillon local d'unités d'exécution à 4 extrémités: 5309
Échantillon de terrain en cours d'exécution
Échantillon de champ 5-End: 4781
Exemple local de thread en cours d'exécution
Échantillon local d'unités d'exécution à 5 extrémités: 5330
Échantillon de terrain en cours d'exécution
Échantillon de champ 6-End: 5294
Exemple local de thread en cours d'exécution
Échantillon local d'unités d'exécution à 6 extrémités: 5511
7-Échantillon de terrain en cours d'exécution
Échantillon de champ 7-End: 5119
Exemple local de thread en cours d'exécution
Échantillon local d'unité d'exécution 7-End: 5793
Échantillon de terrain en cours d'exécution
Échantillon de champ 8-End: 4977
Exemple local de thread en cours d'exécution
Échantillon local d'unités d'exécution à 8 extrémités: 6374
Échantillon de terrain en cours d'exécution 9
Échantillon de champ 9-End: 4841
Échantillon local de thread en cours d'exécution 9
Échantillon local d'unité d'exécution 9-End: 5471
terrain moyen: 5051
ThreadLocal moy: 5664
Env:
openjdk version " 1.8.0_131 "
Intel & # 174; Noyau & # 8482; CPU i7-7500U à 2,70 GHz & # 215; 4
Ubuntu 16.04 LTS
@Pete est le test correct avant l'optimisation.
Je serais très surpris que la construction d'un MessageDigest ait un coût important en termes de frais généraux par rapport à son utilisation réelle.
Manquer en utilisant ThreadLocal peut être une source de fuites et de références pendantes, dont le cycle de vie n’est pas clair, en général, je n’utilise jamais ThreadLocal sans un plan très clair sur le moment où une ressource particulière sera supprimée.
Construisez-le et mesurez-le.
De plus, vous n'avez besoin que d'un seul threadlocal si vous encapsulez le comportement de digestion du message dans un objet. Si vous avez besoin d'un MessageDigest local et d'un octet local [1000] à certaines fins, créez un objet avec un champ messageDigest et un champ d'octet [] et placez cet objet dans le ThreadLocal plutôt que les deux individuellement.