W Javie, jaki byłby najszybszy sposób na iterację wszystkich znaków w łańcuchu, to:
String str = "a really, really long string";
for (int i = 0, n = str.length(); i < n; i++) {
char c = str.charAt(i);
}
Albo to:
char[] chars = str.toCharArray();
for (int i = 0, n = chars.length; i < n; i++) {
char c = chars[i];
}
EDYTOWAĆ :
Chciałbym wiedzieć, czy koszt wielokrotnego wywoływania charAt
metody podczas długiej iteracji kończy się albo mniejszym, albo większym niż koszt wykonania pojedynczego wywołania toCharArray
na początku, a następnie bezpośredniego dostępu do tablicy podczas iteracji.
Byłoby wspaniale, gdyby ktoś mógł dostarczyć solidny punkt odniesienia dla różnych długości strun, mając na uwadze czas rozgrzewania JIT, czas uruchamiania JVM itp., A nie tylko różnicę między dwoma wywołaniami do System.currentTimeMillis()
.
for (char c : chars)
?charAt
kończy się albo mniejszy lub większy niż koszt wykonywania pojedynczego połączenia dotoCharArray
Odpowiedzi:
PIERWSZA AKTUALIZACJA: Zanim wypróbujesz to kiedykolwiek w środowisku produkcyjnym (niezalecane), przeczytaj najpierw: http://www.javaspecialists.eu/archive/Issue237.html Począwszy od Java 9, opisane rozwiązanie nie będzie już działać , ponieważ teraz Java będzie domyślnie przechowywać łańcuchy jako bajt [].
DRUGA AKTUALIZACJA: od 25.10.2016 r. Na moim AMDx64 8core i source 1.8 nie ma różnicy między używaniem „charAt” a dostępem do pola. Wygląda na to, że jvm jest wystarczająco zoptymalizowany, aby wbudowywać i usprawniać wszelkie wywołania „string.charAt (n)”.
Wszystko zależy od długości
String
badanego. Jeśli, jak mówi pytanie, dotyczy to długich ciągów, najszybszym sposobem sprawdzenia łańcucha jest użycie odbicia w celu uzyskania dostępu do kopiichar[]
łańcucha.W pełni losowy test porównawczy z JDK 8 (win32 i win64) na 64 AMD Phenom II 4 core 955 @ 3,2 GHZ (zarówno w trybie klienta, jak iw trybie serwera) z 9 różnymi technikami (patrz poniżej!) Pokazuje, że używanie
String.charAt(n)
jest najszybsze w przypadku małych stringi, a użyciereflection
do uzyskania dostępu do tablicy zaplecza String jest prawie dwukrotnie szybsze w przypadku dużych ciągów.EKSPERYMENT
Wypróbowano 9 różnych technik optymalizacji.
Cała zawartość ciągu jest losowa
Test jest wykonywany dla rozmiarów strun w wielokrotności dwóch, zaczynając od 0,1,2,4,8,16 itd.
Testy są wykonywane 1000 razy na rozmiar łańcucha
Testy są za każdym razem tasowane w losowej kolejności. Innymi słowy, testy są wykonywane w losowej kolejności za każdym razem, gdy są wykonywane, ponad 1000 razy.
Cały zestaw testów jest wykonywany do przodu i do tyłu, aby pokazać wpływ rozgrzewania maszyny JVM na optymalizację i czasy.
Cały zestaw odbywa się dwukrotnie, raz w
-client
trybie, a drugi w-server
trybie.WNIOSKI
tryb klienta (32 bity)
W przypadku ciągów o długości od 1 do 256 znaków wywołanie
string.charAt(i)
wygrywa ze średnim przetwarzaniem od 13,4 do 588 milionów znaków na sekundę.Ponadto jest ogólnie 5,5% szybszy (klient) i 13,9% (serwer) w następujący sposób:
niż w ten sposób z lokalną zmienną końcową długości:
W przypadku długich ciągów znaków o długości od 512 do 256 K , użycie odbicia w celu uzyskania dostępu do tablicy zaplecza String jest najszybsze. Ta technika jest prawie dwa razy szybsza niż String.charAt (i) (o 178% szybciej). Średnia prędkość w tym zakresie wynosiła 1,111 miliarda znaków na sekundę.
Pole należy uzyskać z wyprzedzeniem, a następnie można je ponownie wykorzystać w bibliotece na różnych ciągach. Co ciekawe, w przeciwieństwie do powyższego kodu, w przypadku dostępu do pola, posiadanie lokalnej zmiennej końcowej długości jest o 9% szybsze niż użycie „chars.length” w sprawdzaniu pętli. Oto jak najszybciej można skonfigurować dostęp do pola:
Specjalne komentarze na temat trybu -server
Dostęp do pola zaczyna się wygrywać po 32-bitowych ciągach znaków w trybie serwera na 64-bitowej maszynie Java na moim komputerze AMD 64. Nie było to widoczne do długości 512 znaków w trybie klienta.
Warto również zauważyć, że myślę, że kiedy uruchamiałem JDK 8 (kompilacja 32-bitowa) w trybie serwera, ogólna wydajność była o 7% wolniejsza zarówno dla dużych, jak i małych ciągów. Było to w przypadku wczesnego wydania JDK 8 z kompilacją 121 grudnia 2013 r. Na razie wydaje się, że tryb serwera 32-bitowego jest wolniejszy niż tryb klienta 32-bitowego.
Biorąc to pod uwagę ... wydaje się, że jedynym trybem serwera, który warto wywołać, jest maszyna 64-bitowa. W przeciwnym razie faktycznie ogranicza wydajność.
W przypadku wersji 32-bitowej działającej
-server mode
na AMD64 mogę powiedzieć:Warto również powiedzieć, że String.chars () (Stream i wersja równoległa) to bust. O wiele wolniej niż w jakikolwiek inny sposób.
Streams
API jest raczej powolny sposób, aby wykonywać operacje ogólnie ciągów.Lista życzeń
Java String może mieć predykat akceptujący zoptymalizowane metody, takie jak include (predykat), forEach (konsument), forEachWithIndex (konsument). Tak więc, bez konieczności znajomości przez użytkownika długości lub powtarzania wywołań metod String, może to
beep-beep beep
przyspieszyć analizowanie bibliotek .Marz dalej :)
Happy Strings!
~ SH
W teście wykorzystano 9 następujących metod testowania ciągu znaków na obecność białych znaków:
„charAt1” - SPRAWDŹ ZAWARTOŚĆ STRINGU W ZWYKŁY SPOSÓB:
„charAt2” - TAKIE SAME, JAK POWYŻEJ, ALE UŻYWAJ String.length () ZAMIAST TWORZENIA OSTATECZNEJ LOKALNEJ int NA DŁUGOŚĆ
„stream” - UŻYJ NOWEJ JAVA-8 String's IntStream I PRZEKAŻ PROGNOZĘ DO SPRAWDZENIA
"streamPara" - TAKIE SAMO JAK POWYŻEJ, ALE OH-LA-LA - JEDŹ RÓWNOLEGLE !!!
„reuse” - WYPEŁNIJ WIELOKROTNEGO UŻYTKU [] ZAWARTOŚCIĄ STRINGS
"new1" - UZYSKAJ NOWĄ KOPIĘ ZNAKU [] Z STRUNU
„nowy2” - TAKIE SAME JAK POWYŻEJ, ALE UŻYWAJ „DLA KAŻDEGO”
"field1" - FANCY !! UZYSKAJ POLE DOSTĘPU DO WEWNĘTRZNEGO ZNAKU STRUNY []
„field2” - TAKIE SAME JAK POWYŻEJ, ALE UŻYJ „DLA KAŻDEGO”
WYNIKI KOMPOZYTOWE DLA
-client
TRYBU KLIENTA (łącznie testy w przód i wstecz)Uwaga: tryb -client z 32-bitową Javą i -server z 64-bitową Javą jest taki sam, jak poniżej na moim komputerze AMD64.
WYNIKI KOMPOZYTOWE DLA
-server
TRYBU SERWEROWEGO (łącznie testy w przód i wstecz)Uwaga: to jest test dla 32-bitowej wersji Java działającej w trybie serwera na AMD64. Tryb serwera dla 64-bitowej Javy był taki sam, jak dla 32-bitowej Javy w trybie klienta, z wyjątkiem tego, że dostęp do pola zaczyna wygrywać po rozmiarze 32 znaków.
PEŁNY URUCHOMIONY KOD PROGRAMU
(aby przetestować na Javie 7 i wcześniejszych, usuń testy dwóch strumieni)
źródło
To tylko mikro-optymalizacja, o którą nie powinieneś się martwić.
zwraca kopię
str
tablic znaków (w JDK zwraca kopię znaków przez wywołanieSystem.arrayCopy
).Poza tym
str.charAt()
sprawdza tylko, czy indeks rzeczywiście znajduje się w granicach i zwraca znak w indeksie tablicy.Pierwsza nie tworzy dodatkowej pamięci w JVM.
źródło
Z ciekawości i dla porównania z odpowiedzią Saint Hill.
Jeśli potrzebujesz przetwarzać duże ilości danych, nie powinieneś używać maszyny JVM w trybie klienta. Tryb klienta nie jest przeznaczony do optymalizacji.
Porównajmy wyniki testów porównawczych @Saint Hill przy użyciu maszyny JVM w trybie klienta i serwera.
Zobacz też: Rzeczywiste różnice między „serwerem java” a „klientem java”?
TRYB KLIENTA:
TRYB SERWERA:
WNIOSEK:
Jak widać, tryb serwera jest znacznie szybszy.
źródło
Pierwsze użycie
str.charAt
powinno być szybsze.Jeśli zagłębisz się w kod źródłowy
String
klasy, zobaczymy, żecharAt
jest zaimplementowany w następujący sposób:Tutaj wszystko, co robi, to indeksowanie tablicy i zwracanie wartości.
Teraz, jeśli zobaczymy implementację
toCharArray
, znajdziemy poniżej:Jak widzisz, robi to,
System.arraycopy
co z pewnością będzie odrobinę wolniejsze niż nie robienie tego.źródło
Pomimo odpowiedzi @Saint Hill, jeśli weźmiesz pod uwagę złożoność czasową str.toCharArray () ,
pierwsza jest szybsza nawet dla bardzo dużych strun. Możesz uruchomić poniższy kod, aby zobaczyć to na własne oczy.
wynik:
źródło
Wygląda na to, że niether jest szybszy lub wolniejszy
Na długie struny wybiorę pierwszy. Po co kopiować długie struny? Dokumentacja mówi:
// Edycja 1
Zmieniłem test, aby oszukać optymalizację JIT.
// Edycja 2
Powtórz test 10 razy, aby JVM się rozgrzał.
// Edycja 3
Wnioski:
Przede wszystkim
str.toCharArray();
kopiuje cały ciąg do pamięci. Długie ciągi mogą zajmować dużo pamięci. MetodaString.charAt( )
wyszukuje znaki w tablicy znaków wewnątrz klasy String sprawdzającej indeks wcześniej. Wygląda na to, że dla wystarczająco krótkich łańcuchów pierwsza metoda (tj.chatAt
Metoda) jest nieco wolniejsza z powodu tego sprawdzenia indeksu. Ale jeśli String jest wystarczająco długi, kopiowanie całej tablicy znaków jest wolniejsze, a pierwsza metoda jest szybsza. Im dłuższa jest struna, tym wolniejtoCharArray
działa. Spróbuj zmienić limit wfor(int j = 0; j < 10000; j++)
pętli, aby to zobaczyć. Jeśli pozwolimy, aby JVM rozgrzał się, kod działa szybciej, ale proporcje są takie same.W końcu to tylko mikro-optymalizacja.
źródło
for:in
opcję, dla samej przyjemności?Iterable
ani tablicą.String.toCharArray()
tworzy nową tablicę znaków, oznacza alokację pamięci o długości łańcucha, następnie kopiuje oryginalną tablicę znaków za pomocą,System.arraycopy()
a następnie zwraca tę kopię do wywołującego. String.charAt () zwraca znak na pozycjii
z oryginalnej kopii, dlategoString.charAt()
będzie szybszy niżString.toCharArray()
. ChociażString.toCharArray()
zwraca copy, a nie znak z oryginalnej tablicy String, gdzieString.charAt()
zwraca znak z oryginalnej tablicy znaków. Poniższy kod zwraca wartość o określonym indeksie tego ciągu.Poniższy kod zwraca nowo przydzieloną tablicę znaków, której długość jest długością tego ciągu
źródło
Drugi powoduje utworzenie nowej tablicy znaków i skopiowanie wszystkich znaków ze String do nowej tablicy znaków, więc domyślam się, że pierwsza jest szybsza (i mniej wymagająca pamięci).
źródło