Sto cercando di leggere un grande corpus di testo in memoria con Java. Ad un certo punto colpisce un muro e solo la spazzatura si raccoglie interminabilmente. Mi piacerebbe sapere se qualcuno ha esperienza a battere il GC di Java in sottomissione con set di dati di grandi dimensioni.Scarse prestazioni con elenchi Java di grandi dimensioni
Sto leggendo un file da 8 GB di testo inglese, in UTF-8, con una frase in una riga. Voglio split()
ogni riga su spazio vuoto e archiviare gli array di stringhe risultanti in un ArrayList<String[]>
per un'ulteriore elaborazione. Ecco un programma semplificato che mostra il problema:
/** 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.");
}
}
Sembra piuttosto tagliato e asciutto, giusto? Noterai che ho anche pre-dimensionato il mio ArrayList
; Ho poco meno di 66 milioni di frasi e 1,3 miliardi di token. Ora se tirate fuori il vostro Java object sizes di riferimento e la matita, troverete che dovrebbe richiedere circa:
- 66e6
String[]
riferimenti @ 8 byte EA = 0.5 GB - 66e6
String[]
oggetti @ 32 byte EA = 2 GB - 66e6
char[]
oggetti @ 32 byte ea = 2 GB - 1.3e9
String
riferimenti @ 8 byte ea = 10 GB - 1.3e9
String
s @ 44 bytes ea = 53 GB 0.123,51641 milioni
- 8e9
char
s @ 2 byte ea = 15 GB
83 GB. (Noterai che ho davvero bisogno di usare dimensioni degli oggetti a 64 bit, dal momento che Compressed OOPs non mi può aiutare con> 32 GB di heap.) Siamo fortunati ad avere una macchina RedHat 6 con 128 GB di RAM, quindi accendo la mia VM server Java HotSpot (TM) a 64 bit (build 20.4-b02, modalità mista) dal mio kit Java SE 1.6.0_29 con pv giant-file.txt | java -Xmx96G -Xms96G LoadTokens
per sicurezza, e mi rilasso mentre guardo top
.
Da qualche parte a meno di metà dell'ingresso, con circa 50-60 GB di RSS, il garbage collector parallelo prende il 1300% della CPU (16 process box) e legge gli arresti di avanzamento. Poi passa ancora un po 'di GB, quindi il progresso si interrompe ancora più a lungo. Riempie 96 GB e non è ancora finito. L'ho lasciato andare per un'ora e mezza, e brucia solo il 90% del tempo di sistema per fare GC. Sembra estremo.
Per assicurarmi di non essere pazzo, ho montato l'equivalente Python (tutte e due le righe;) ed è stato completato in circa 12 minuti e 70 GB RSS.
Quindi: sto facendo qualcosa di stupido? (A parte il modo generalmente inefficiente di memorizzare le cose, che non posso davvero aiutare - e anche se le mie strutture dati sono grasse, a patto che si adattino, Java non dovrebbe solo soffocare.) C'è magia Consulenza GC per cumuli veramente grandi? Ho provato -XX:+UseParNewGC
e sembra ancora peggio.
Dove si trovano gli oggetti 'char []' che supportano le stringhe? –
Negli oggetti 'String': intestazione oggetto a 24 byte + puntatore a 8 byte' char [] '+ 4 byte start, offset e hashcode, se i miei calcoli sono corretti. –
Questo è il 'char []' * reference * - ma che dire degli stessi 'char []' * objects *? Un array 'char []' ha anche un overhead di oggetto ... –