Próbuję odczytać duży korpus tekstowy do pamięci za pomocą Javy. W pewnym momencie uderza w ścianę i tylko śmieci zbierają się w nieskończoność. Chciałbym wiedzieć, czy ktokolwiek ma doświadczenie z pobieraniem GC Java do przesyłania z dużymi zbiorami danych.Słaba wydajność z dużymi listami Java
Czytam plik 8 GB tekstu w języku angielskim, w UTF-8, z jednym zdaniem do wiersza. Chcę split()
każdej linii na białych znakach i przechowywać wynikowe tablice String w ArrayList<String[]>
w celu dalszego przetwarzania. Oto uproszczony program, w którym występuje problem:
/** Load whitespace-delimited tokens from stdin into memory. */
public class LoadTokens {
private static final int INITIAL_SENTENCES = 66000000;
public static void main(String[] args) throws IOException {
List<String[]> sentences = new ArrayList<String[]>(INITIAL_SENTENCES);
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
long numTokens = 0;
String line;
while ((line = stdin.readLine()) != null) {
String[] sentence = line.split("\\s+");
if (sentence.length > 0) {
sentences.add(sentence);
numTokens += sentence.length;
}
}
System.out.println("Read " + sentences.size() + " sentences, " + numTokens + " tokens.");
}
}
Wydaje się być dobrze przycięty i wysuszony, prawda? Zauważysz, że nawet wstępnie zmieniłem rozmiar mojego ArrayList
; Mam niewiele mniej niż 66 milionów zdań i 1,3 miliarda żetonów. Teraz, jeśli bat na odniesienie Java object sizes i ołówka, przekonasz się, że powinno wymagać około:
- 66e6
String[]
referencje @ 8 bajtów EA = 0,5 GB - 66e6
String[]
obiekty @ 32 bajty EA = 2 GB - 66e6
char[]
obiekty @ 32 bajtów EA = 2 PL - 1.3e9
String
odniesienia @ 8 bajtów EA = 10 PL - 1.3e9
String
s @ 44 bajtów EA = 53 PL - 8E9
char
S @ 2 bajty Ea = 15 PL
83 PL. (Zauważysz, że naprawdę potrzebuję używać 64-bitowych rozmiarów obiektów, ponieważ Compressed OOPs nie może mi pomóc z stertą o wielkości 32 GB.) Mamy szczęście mieć maszynę RedHat 6 z 128 GB pamięci RAM, więc odpalam moja 64-bitowa maszyna wirtualna Java HotSpot (kompilacja 20.4-b02, tryb mieszany) z mojego zestawu Java SE 1.6.0_29 z pv giant-file.txt | java -Xmx96G -Xms96G LoadTokens
tylko po to, aby być bezpiecznym i cofnąć się, gdy oglądam top
.
Mniej niż w połowie wprowadzania danych, przy około 50-60 GB RSS, równoległy moduł do zbierania śmieci uruchamia procesor do 1300% (skrzynka 16-procesowa) i zatrzymuje zatrzymanie postępu. Potem idzie o kilka więcej GB, a potem postęp zatrzymuje się jeszcze dłużej. Wypełnia 96 GB i nie jest jeszcze gotowy. Pozwoliłem, by trwało to półtorej godziny, i to po prostu spalenie ~ 90% czasu systemowego robi GC. To wydaje się ekstremalne.
Aby upewnić się, że nie jestem szalony, pobudziłem odpowiednik Pythona (wszystkie dwie linie;) i uruchomiono go do ukończenia w około 12 minut i 70 GB RSS.
Więc: czy robię coś głupiego? (Poza ogólnie nieefektywnym sposobem przechowywania rzeczy, których naprawdę nie mogę pomóc - i nawet jeśli moje struktury danych są grube, tak długo jak będą pasowały, Java nie powinna po prostu udusić.) Czy jest magia Porady GC dla naprawdę dużych stert? Spróbowałem -XX:+UseParNewGC
i wygląda na to, że jest jeszcze gorzej.
Gdzie są obiekty "char []" wspierające łańcuchy? –
W obiektach "String": 24-bajtowy nagłówek obiektu + 8-bajtowy "char []" wskaźnik + 4-bajtowe początkowe, offsetowe i hashcode, jeśli moje obliczenia są poprawne. –
To jest "char []" * * * - ale co z obiektami char [] * *? Tablica 'char []' ma również obiekt nadrzędny ... –