Wie viel wird von ThreadLocal
Variablen langsamer gelesen als von regulären Feldern?
Konkreter ist die einfache Objekterstellung schneller oder langsamer als der Zugriff auf ThreadLocal
Variablen?
Ich gehe davon aus, dass es schnell genug ist, so dass es ThreadLocal<MessageDigest>
viel schneller ist, eine Instanz zu haben, als MessageDigest
jedes Mal eine Instanz zu erstellen . Aber gilt das zum Beispiel auch für Byte [10] oder Byte [1000]?
Edit: Frage ist, was wirklich los ist, wenn wir anrufen ThreadLocal
? Wenn das nur ein Feld ist, wie jedes andere, dann wäre die Antwort "es ist immer am schnellsten", oder?
Thread
enthalten s eine (nicht synchronisierte) Hashmap, in der der Schlüssel das aktuelleThreadLocal
Objekt istAntworten:
Das Ausführen unveröffentlichter Benchmarks
ThreadLocal.get
dauert auf meinem Computer ungefähr 35 Zyklen pro Iteration. Nicht viel. In der Implementierung von Sun wird eine benutzerdefinierte lineare Prüf-Hash-Karte inThread
KartenThreadLocal
s auf Werte abgebildet. Da nur ein einzelner Thread darauf zugreift, kann es sehr schnell gehen.Die Zuordnung kleiner Objekte dauert ähnlich viele Zyklen, obwohl Sie aufgrund der Cache-Erschöpfung in einer engen Schleife möglicherweise etwas niedrigere Zahlen erhalten.
Der Bau von
MessageDigest
ist wahrscheinlich relativ teuer. Es hat eine ganze Menge Staat und der Bau geht durch denProvider
SPI-Mechanismus. Möglicherweise können Sie die Optimierung durchführen, indem Sie beispielsweise die Daten klonen oder bereitstellenProvider
.Nur weil das Zwischenspeichern in einem
ThreadLocal
System möglicherweise schneller ist als das Erstellen, bedeutet dies nicht zwangsläufig, dass die Systemleistung steigt. Sie haben zusätzliche Gemeinkosten im Zusammenhang mit GC, die alles verlangsamen.Sofern Ihre Anwendung nicht sehr häufig verwendet wird, sollten
MessageDigest
Sie stattdessen einen herkömmlichen thread-sicheren Cache verwenden.quelle
new org.bouncycastle.crypto.digests.SHA1Digest()
. Ich bin mir ziemlich sicher, dass kein Cache es schlagen kann.Im Jahr 2009 implementierten einige JVMs ThreadLocal mithilfe einer nicht synchronisierten HashMap im Thread.currentThread () -Objekt. Dies machte es extrem schnell (wenn auch nicht annähernd so schnell wie die Verwendung eines normalen Feldzugriffs) und stellte sicher, dass das ThreadLocal-Objekt aufgeräumt wurde, als der Thread starb. Bei der Aktualisierung dieser Antwort im Jahr 2016 scheinen die meisten (alle?) Neueren JVMs eine ThreadLocalMap mit linearer Prüfung zu verwenden. Ich bin mir über die Leistung dieser nicht sicher - aber ich kann mir nicht vorstellen, dass sie wesentlich schlechter ist als die frühere Implementierung.
Natürlich ist new Object () heutzutage auch sehr schnell, und die Garbage Collectors sind auch sehr gut darin, kurzlebige Objekte zurückzugewinnen.
Wenn Sie nicht sicher sind, dass die Objekterstellung teuer sein wird, oder wenn Sie einen Status auf Thread-für-Thread-Basis beibehalten müssen, sollten Sie sich für die einfachere Zuweisung bei Bedarf entscheiden und erst dann zu einer ThreadLocal-Implementierung wechseln, wenn a Der Profiler sagt Ihnen, dass Sie müssen.
quelle
Gute Frage, das habe ich mir kürzlich gestellt. Um Ihnen eindeutige Zahlen zu geben, die folgenden Benchmarks (in Scala mit praktisch denselben Bytecodes wie der entsprechende Java-Code kompiliert):
hier verfügbar , wurden auf einem AMD 4x 2,8 GHz Dual-Cores und einem Quad-Core i7 mit Hyperthreading (2,67 GHz) durchgeführt.
Das sind die Zahlen:
i7
Technische Daten: Intel i7 2x Quad-Core bei 2,67 GHz Test: scala.threads.ParallelTests
Testname: loop_heap_read
Fadennummer: 1 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5 an) 9.0069 9.0036 9.0017 9.0084 9.0074 (Durchschnitt = 9.1034 min = 8.9986 max = 21.0306)
Fadennummer: 2 Gesamtprüfungen: 200
Laufzeiten: (zeigt die letzten 5) 4.5563 4.7128 4.5663 4.5617 4.5724 (Durchschnitt = 4.6337 min = 4.5509 max = 13.9476)
Fadennummer: 4 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 2.3946 2.3979 2.3934 2.3937 2.3964 (Durchschnitt = 2.5113 min = 2.3884 max = 13.5496)
Fadennummer: 8 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 2.4479 2.4362 2.4323 2.4472 2.4383 (Durchschnitt = 2.5562 min = 2.4166 max = 10.3726)
Testname: threadlocal
Fadennummer: 1 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 91.1741 90.8978 90.6181 90.6200 90.6113 (Durchschnitt = 91.0291 min = 90.6000 max = 129.7501)
Fadennummer: 2 Gesamtprüfungen: 200
Laufzeiten: (zeigt die letzten 5) 45.3838 45.3858 45.6676 45.3772 45.3839 (Durchschnitt = 46.0555 min = 45.3726 max = 90.7108)
Fadennummer: 4 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 22.8118 22.8135 59.1753 22.8229 22.8172 (Durchschnitt = 23.9752 min = 22.7951 max = 59.1753)
Fadennummer: 8 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 22.2965 22.2415 22.3438 22.3109 22.4460 (Durchschnitt = 23.2676 min = 22.2346 max = 50.3583)
AMD
Technische Daten: AMD 8220 4x Dual-Core bei 2,8 GHz Test: scala.threads.ParallelTests
Testname: loop_heap_read
Gesamtarbeit: 20000000 Fadennummer: 1 Gesamtprüfungen: 200
Laufzeiten: (zeigt die letzten 5) 12.625 12.631 12.634 12.632 12.628 (Durchschnitt = 12.7333 min = 12.619 max = 26.698)
Testname: loop_heap_read Gesamtarbeit: 20000000
Laufzeiten: (zeigt die letzten 5) 6.412 6.424 6.408 6.397 6.43 (Durchschnitt = 6.5367 min = 6.393 max = 19.716)
Fadennummer: 4 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 3.385 4.298 9.7 6.535 3.385 (Durchschnitt = 5.6079 min = 3.354 max = 21.603)
Fadennummer: 8 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 5.389 5.795 10.818 3.823 3.824 (Durchschnitt = 5.5810 min = 2.405 max = 19.755)
Testname: threadlocal
Fadennummer: 1 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5 an) 200,217 207,335 200,241 207,342 200,23 (Durchschnitt = 202,2424 min = 200,184 max = 245,369)
Fadennummer: 2 Gesamtprüfungen: 200
Laufzeiten: (zeigt die letzten 5) 100.208 100.199 100.211 103.781 100.215 (Durchschnitt = 102.2238 min = 100.192 max = 129.505)
Fadennummer: 4 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 62.101 67.629 62.087 52.021 55.766 (Durchschnitt = 65.6361 min = 50.282 max = 167.433)
Fadennummer: 8 Gesamttests: 200
Laufzeiten: (zeigt die letzten 5) 40.672 74.301 34.434 41.549 28.119 (Durchschnitt = 54.7701 min = 28.119 max = 94.424)
Zusammenfassung
Ein lokaler Thread ist ungefähr 10-20x so groß wie der des gelesenen Heaps. Es scheint auch gut auf diese JVM-Implementierung und diese Architekturen mit der Anzahl der Prozessoren zu skalieren.
quelle
"!"
in der ersten Methode aus dem Speicher gelesen (das Schreiben von erfolgt nie). Die erste Methode entspricht praktisch der UnterklasseThread
und dem Zuweisen eines benutzerdefinierten Felds. Der Benchmark misst einen extremen Randfall, bei dem die gesamte Berechnung aus dem Lesen einer Variablen / eines lokalen Threads besteht. Reale Anwendungen sind möglicherweise nicht von ihrem Zugriffsmuster betroffen, verhalten sich jedoch im schlimmsten Fall wie oben.Hier geht es noch einen Test. Die Ergebnisse zeigen, dass ThreadLocal etwas langsamer als ein reguläres Feld ist, jedoch in derselben Reihenfolge. Ca. 12% langsamer
Ausgabe:
0-laufendes Feldbeispiel
0-End Feldprobe: 6044
0-laufendes lokales Thread-Beispiel
Lokales Beispiel für 0-End-Thread: 6015
1-Lauffeldprobe
1-End-Feldprobe: 5095
1-laufendes lokales Thread-Beispiel
Lokales Beispiel für 1-End-Thread: 5720
2-Lauffeldprobe
2-End-Feldprobe: 4842
2-laufendes lokales Thread-Beispiel
Lokales Beispiel für ein 2-End-Gewinde: 5835
3-Lauffeldprobe
3-End-Feldprobe: 4674
3-laufendes lokales Thread-Beispiel
Lokales 3-End-Thread-Beispiel: 5287
4-Lauffeldprobe
4-End-Feldprobe: 4849
4-laufendes lokales Thread-Beispiel
Lokales 4-End-Thread-Beispiel: 5309
5-Lauffeldprobe
5-End-Feldprobe: 4781
5-Ausführen eines lokalen Thread-Beispiels
Lokales 5-End-Thread-Beispiel: 5330
6-Lauffeldprobe
6-End-Feldprobe: 5294
6-Ausführen eines lokalen Thread-Beispiels
Lokales 6-End-Thread-Beispiel: 5511
7-Lauffeldprobe
7-End-Feldprobe: 5119
7-Ausführen eines lokalen Thread-Beispiels
Lokales 7-End-Thread-Beispiel: 5793
8-Lauffeldprobe
8-End-Feldprobe: 4977
8-laufendes lokales Thread-Beispiel
Lokales Beispiel für 8-End-Thread: 6374
9-Lauffeldprobe
9-End Feldprobe: 4841
9-Ausführen eines lokalen Thread-Beispiels
Lokales 9-End-Thread-Beispiel: 5471
Felddurchschn.: 5051
ThreadLocal-Durchschnitt: 5664
Env:
openjdk version "1.8.0_131"
Intel® Core ™ i7-7500U CPU bei 2,70 GHz × 4
Ubuntu 16.04 LTS
quelle
Int.toString)
was im Vergleich zu dem, was Sie testen, extrem teuer ist. B) Sie führen bei jeder Iteration zwei Map-Ops durch, auch völlig unabhängig und teuer. Versuchen Sie stattdessen, ein primitives int aus ThreadLocal zu erhöhen. C) Verwenden SieSystem.nanoTime
stattSystem.currentTimeMillis
, das erstere dient der Profilerstellung, das letztere dient der Datums- und Uhrzeit des Benutzers und kann sich unter Ihren Füßen ändern. D) Sie sollten Zuweisungen vollständig vermeiden, einschließlich der Zuweisungen der obersten Ebene für Ihre "Beispiel" -Klassen@Pete ist korrekter Test, bevor Sie optimieren.
Ich wäre sehr überrascht, wenn das Erstellen eines MessageDigest im Vergleich zur tatsächlichen Verwendung einen ernsthaften Overhead hätte.
Die fehlende Verwendung von ThreadLocal kann eine Quelle für Lecks und baumelnde Referenzen sein, die keinen eindeutigen Lebenszyklus haben. Im Allgemeinen verwende ich ThreadLocal nie ohne einen sehr klaren Plan, wann eine bestimmte Ressource entfernt wird.
quelle
Bauen Sie es und messen Sie es.
Außerdem benötigen Sie nur einen Threadlocal, wenn Sie Ihr Nachrichtenverdauungsverhalten in ein Objekt einkapseln. Wenn Sie für einen bestimmten Zweck ein lokales MessageDigest und ein lokales Byte [1000] benötigen, erstellen Sie ein Objekt mit einem messageDigest- und einem Byte [] -Feld und fügen Sie dieses Objekt in den ThreadLocal ein, anstatt beide einzeln.
quelle