Ile jest odczytywane ze ThreadLocal
zmiennej wolniej niż ze zwykłego pola?
Bardziej konkretnie, czy proste tworzenie obiektów jest szybsze czy wolniejsze niż dostęp do ThreadLocal
zmiennej?
Zakładam, że jest na tyle szybki, że posiadanie ThreadLocal<MessageDigest>
instancji jest znacznie szybsze niż tworzenie instancji za MessageDigest
każdym razem. Ale czy dotyczy to na przykład bajtu [10] lub bajtu [1000]?
Edycja: Pytanie, co się naprawdę dzieje, gdy dzwonisz ThreadLocal
do get? Jeśli to tylko pole, jak każde inne, odpowiedź brzmiałaby: „zawsze jest najszybciej”, prawda?
Thread
s zawierają (niezsynchronizowane) mapy hasłowe, w których kluczem jest bieżącyThreadLocal
obiektOdpowiedzi:
Uruchomienie niepublikowanych testów porównawczych
ThreadLocal.get
zajmuje około 35 cykli na iterację na moim komputerze. Niezbyt dużo. W implementacji firmy Sun niestandardowa mapa mieszania z sondowaniem liniowym wThread
mapachThreadLocal
s na wartości. Ponieważ jest dostępny tylko przez jeden wątek, może być bardzo szybki.Alokacja małych obiektów zajmuje podobną liczbę cykli, chociaż z powodu wyczerpania pamięci podręcznej można uzyskać nieco niższe wartości w ciasnej pętli.
Budowa
MessageDigest
prawdopodobnie będzie stosunkowo droga. Ma sporo stanu, a konstrukcja przechodzi przezProvider
mechanizm SPI. Możesz być w stanie zoptymalizować, na przykład, klonując lub udostępniającProvider
.To, że buforowanie
ThreadLocal
zamiast tworzenia może być szybsze, niekoniecznie oznacza, że wydajność systemu wzrośnie. Będziesz miał dodatkowe narzuty związane z GC, które spowalniają wszystko.O ile Twoja aplikacja nie używa bardzo intensywnie,
MessageDigest
możesz rozważyć użycie zamiast tego konwencjonalnej pamięci podręcznej bezpiecznej dla wątków.źródło
new org.bouncycastle.crypto.digests.SHA1Digest()
. Jestem pewien, że żadna pamięć podręczna nie może tego pokonać.W 2009 roku niektóre maszyny JVM zaimplementowały ThreadLocal przy użyciu niezsynchronizowanej mapy HashMap w obiekcie Thread.currentThread (). To sprawiło, że było to niezwykle szybkie (choć oczywiście nie tak szybkie, jak przy użyciu zwykłego dostępu do pola), a także zapewniło, że obiekt ThreadLocal został uporządkowany, gdy wątek umarł. Aktualizując tę odpowiedź w 2016 r., Wydaje się, że większość (wszystkich?) Nowszych maszyn JVM używa ThreadLocalMap z sondowaniem liniowym. Nie jestem pewien co do ich wydajności - ale nie wyobrażam sobie, że jest znacznie gorsza niż wcześniejsza realizacja.
Oczywiście, nowy Object () jest również obecnie bardzo szybki, a Garbage Collectors są również bardzo dobre w odzyskiwaniu krótkotrwałych obiektów.
O ile nie masz pewności, że tworzenie obiektów będzie kosztowne lub musisz zachować pewien stan w wątku po wątku, lepiej jest wybrać prostsze rozwiązanie alokacji w razie potrzeby i przełączenie się na implementację ThreadLocal tylko wtedy, gdy profiler mówi, że musisz.
źródło
Dobre pytanie, zadawałem sobie to ostatnio pytanie. Aby podać konkretne liczby, poniższe testy porównawcze (w Scali, skompilowane do praktycznie tych samych kodów bajtowych, co równoważny kod Java):
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) }
dostępne tutaj , zostały wykonane na dwurdzeniowych procesorach AMD 4x 2,8 GHz i czterordzeniowych procesorach i7 z hiperwątkowością (2,67 GHz).
Oto liczby:
i7
Specyfikacja: Czterordzeniowy procesor Intel i7 2x @ 2,67 GHz Test: scala.thread.ParallelTests
Nazwa testu: loop_heap_read
Numer wątku: 1 Wszystkich testów: 200
Czasy działania: (pokazuje ostatnie 5) 9,0069 9,0036 9,0017 9,0084 9,0074 (śr. = 9,1034 min = 8,9986 maks. = 21,0306)
Numer wątku: 2 Testy ogółem: 200
Czasy pracy: (pokazuje ostatnie 5) 4,5563 4,7128 4,5663 4,5617 4,5724 (śr. = 4,6337 min = 4,5509 maks. = 13,9476)
Numer wątku: 4 Wszystkich testów: 200
Czasy działania: (pokazuje ostatnie 5) 2,3946 2,3979 2,3934 2,3937 2,3964 (śr. = 2,5113 min = 2,3884 maks. = 13,5496)
Numer wątku: 8 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 2,4479 2,4362 2,4323 2,4472 2,4383 (śr. = 2,5562 min = 2,4166 maks. = 10,3726)
Nazwa testu: threadlocal
Numer wątku: 1 Wszystkich testów: 200
Czasy działania: (pokazuje ostatnie 5) 91,1741 90,8978 90,6181 90,6200 90,6113 (śr. = 91,0291 min = 90,6000 maks. = 129,7501)
Numer wątku: 2 Testy ogółem: 200
Czasy pracy: (pokazuje ostatnie 5) 45,3838 45,3858 45,6676 45,3772 45,3839 (śr. = 46,0555 min = 45,3726 maks. = 90,7108)
Numer wątku: 4 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 22,8118 22,8135 59,1753 22,8229 22,8172 (śr. = 23,9752 min = 22,7951 maks. = 59,1753)
Numer wątku: 8 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 22,2965 22,2415 22,3438 22,3109 22,4460 (śr. = 23,2676 min = 22,2346 maks. = 50,3583)
AMD
Specyfikacja: Dwurdzeniowy procesor AMD 8220 4x @ 2,8 GHz Test: scala.thread.ParallelTests
Nazwa testu: loop_heap_read
Wszystkich prac: 20000000 Numer wątku: 1 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 12,625 12,631 12,634 12,632 12,628 (śr. = 12,7333 min = 12,619 maks. = 26,698)
Nazwa testu: loop_heap_read Całkowita praca: 20000000
Czasy pracy: (pokazuje ostatnie 5) 6,412 6,424 6,408 6,397 6,43 (śr. = 6,5367 min = 6,393 maks. = 19,716)
Numer wątku: 4 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 3,385 4,298 9,7 6,535 3,385 (śr. = 5,6079 min = 3,354 maks. = 21,603)
Numer wątku: 8 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 5,389 5,795 10,818 3,823 3,824 (śr. = 5,5810 min = 2,405 maks. = 19,755)
Nazwa testu: threadlocal
Numer wątku: 1 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 200,217 207,335 200,241 207,342 200,23 (śr. = 202,2424 min = 200,184 maks. = 245,369)
Numer wątku: 2 Testy ogółem: 200
Czasy pracy: (pokazuje ostatnie 5) 100,208 100,199 100,211 103,781 100,215 (śr. = 102,2238 min = 100,192 maks. = 129,505)
Numer wątku: 4 Wszystkich testów: 200
Czasy pracy: (pokazuje ostatnie 5) 62,101 67,629 62,087 52,021 55,766 (śr. = 65,6361 min = 50,282 maks. = 167,433)
Numer wątku: 8 Wszystkich testów: 200
Czasy działania: (pokazuje ostatnie 5) 40,672 74,301 34,434 41,549 28,119 (śr. = 54,7701 min. = 28,119 maks. = 94,424)
Podsumowanie
Wątek lokalny jest około 10–20 razy większy niż odczyt sterty. Wydaje się również, że dobrze skaluje się w tej implementacji JVM i tych architekturach z liczbą procesorów.
źródło
"!"
nigdy nie występuje) w pierwszej metodzie - pierwsza metoda jest efektywnie równoważna z podklasąThread
i nadaniem jej własnego pola. Benchmark mierzy skrajny przypadek skrajny, w którym całe obliczenia składają się z odczytu zmiennej / wątku lokalnego - w zależności od ich wzorca dostępu może nie mieć to wpływu na rzeczywiste aplikacje, ale w najgorszym przypadku będą one zachowywać się jak powyżej.Oto kolejny test. Wyniki pokazują, że ThreadLocal jest nieco wolniejszy niż zwykłe pole, ale w tej samej kolejności. Około 12% wolniej
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); } } }'
Wynik:
0-próbka terenowa
Próbka pola 0-End: 6044
0-uruchomiona próbka lokalna wątku
Próbka lokalna gwintu 0-końca: 6015
1-próbka terenowa
Próbka z 1-końcowym polem: 5095
1-uruchomiony wątek, próbka lokalna
Próbka lokalna gwintu z jednym końcem: 5720
2-biegowa próbka terenowa
Próbka z 2 końcówkami: 4842
2-uruchomiona próbka lokalna wątku
Próbka lokalna z gwintem 2-końcowym: 5835
3-próbka terenowa
Próbka pola z 3 końcówkami: 4674
Próbka lokalna z 3 uruchomionym wątkiem
Próbka lokalna z gwintem 3-końcowym: 5287
4-biegowa próbka terenowa
Próbka z 4 końcówkami: 4849
Próbka lokalna 4-uruchomionego wątku
Próbka lokalna z gwintem 4-końcowym: 5309
5-próbka terenowa
Próbka pola 5-końcowego: 4781
5-uruchomiona próbka lokalna wątku
Próbka lokalna z 5-końcowym gwintem: 5330
6-próbka terenowa
Próbka pola 6-End: 5294
6-uruchomiony wątek lokalny przykład
Próbka lokalna z gwintem 6-końcowym: 5511
7-próbka terenowa
Próbka pola 7-End: 5119
7-uruchomiony wątek lokalnej próbki
Próbka lokalna wątku 7-końcowego: 5793
8-próbka terenowa
Próbka pola 8-End: 4977
8-uruchomiony wątek lokalny przykład
Próbka lokalna z 8-końcowym gwintem: 6374
9-próbka terenowa
Próbka pola 9-End: 4841
9-uruchomiona próbka lokalna wątku
Próbka lokalna z gwintem 9-końcowym: 5471
Średnia z pola: 5051
ThreadLocal avg: 5664
Koperta:
wersja openjdk „1.8.0_131”
Procesor Intel® Core ™ i7-7500U przy 2,70 GHz × 4
Ubuntu 16.04 LTS
źródło
Int.toString)
co jest niezwykle kosztowne w porównaniu z tym, co testujesz. B) wykonujesz dwie operacje na mapie w każdej iteracji, również całkowicie niepowiązane i drogie. Spróbuj zamiast tego zwiększyć prymitywny int z ThreadLocal. C) stosowanieSystem.nanoTime
zamiastSystem.currentTimeMillis
, ta pierwsza jest do profilowania, drugi jest dla użytkowników celach Date-Time i mogą ulec zmianie pod swoimi stopami. D) Powinieneś całkowicie unikać alokacji, w tym tych najwyższego poziomu na swoje „przykładowe” zajęcia@Pete to poprawny test przed optymalizacją.
Byłbym bardzo zaskoczony, gdyby konstruowanie MessageDigest wiązało się z jakimś poważnym narzutem w porównaniu z jego aktywnym użyciem.
Pominięcie korzystania z ThreadLocal może być źródłem wycieków i wiszących odniesień, które nie mają jasnego cyklu życia, generalnie nigdy nie używam ThreadLocal bez bardzo jasnego planu, kiedy dany zasób zostanie usunięty.
źródło
Zbuduj to i zmierz.
Ponadto potrzebujesz tylko jednego wątku lokalnego, jeśli umieścisz zachowanie przetwarzania wiadomości w obiekcie. Jeśli do jakiegoś celu potrzebujesz lokalnego MessageDigest i lokalnego bajtu [1000], utwórz obiekt z polem messageDigest i bajtem [] i umieść ten obiekt w ThreadLocal zamiast obu osobno.
źródło