2017-01-03 11 views
0

Ich mache tief lernen neuronalen Netzentwicklung, mit dem MNIST-Datensatz zum Testen. Der Trainingssatz besteht aus 60.000 Sequenzen mit jeweils 784 Eingabewerten mit doppelter Genauigkeit. Der Prozess des Lesens dieser Daten aus der Datei in ein Array in Java verursacht irgendwie einen Arbeitsspeicher-Overhead von ungefähr 4 GB, der während des Laufs des Programms zugeordnet bleibt. Dieser Overhead ist zusätzlich zu den 60000 · 784 · 8 = 376 MB, die für das Array mit doppelter Genauigkeit selbst zugewiesen sind. Es scheint wahrscheinlich, dass dieser Overhead auftritt, weil Java eine vollständige Kopie der Datei in einem Zusatz zu dem numerischen Array speichert, aber dies ist möglicherweise Scanner-Overhead.Großer Speicheraufwand beim Lesen einer großen Datendatei in Java

Laut einer Quelle wird beim Lesen der Datei als Stream vermieden, dass die gesamte Datei im Speicher abgelegt wird. Allerdings habe ich immer noch dieses Problem mit einem Stream gelesen. Ich verwende Java 8 mit Intellij 2016.2.4. Dies ist der Strom-Lesecode:

FileInputStream inputStream = null; 
Scanner fileScan = null; 
String line; 
String[] numbersAsStrings; 

totalTrainingSequenceArray = new double[60000][784]; 

try { 
    inputStream = new FileInputStream(m_sequenceFile); 
    fileScan = new Scanner(inputStream, "UTF-8"); 
    int sequenceNum = 0; 
    line = fileScan.nextLine();//Read and discard the first line. 
    while (fileScan.hasNextLine()) { 
     line = fileScan.nextLine(); 
     numbersAsStrings = line.split("\\s+"); //Split the line into an array of strings using any whitespace delimiter. 
     for (int inputPosition = 0; inputPosition < m_numInputs; inputPosition++) { 
      totalTrainingSequenceArray[sequenceNum][inputPosition] = Double.parseDouble(numbersAsStrings[inputPosition]); 
     } 
     sequenceNum++; 
    } 
    if (fileScan.ioException() != null) {//Handle fileScan exception 
     throw fileScan.ioException(); 
    } 
} catch (IOException e) {//Handle the inputstream exception 
    e.printStackTrace(); 
} finally { 
    if (inputStream != null) { 
     try { 
      inputStream.close(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 
    if (fileScan != null) { 
     fileScan.close(); 
    } 
} 

Ich habe versucht, den Strom und den Scanner auf null, nachdem die Leseeinstellung und Aufruf System.gc(), aber das tut nichts. Ist dies ein Problem mit dem Scanner-Overhead? Was wäre der einfachste Weg, diese große Datei zu lesen, ohne großen permanenten Overhead zu verursachen? Danke für jede Eingabe.

+1

Wie messen Sie die Speichernutzung? – NPE

+0

Wenn Sie Java 8 verwenden, können Sie von der 'Files.lines()' Methode profitieren. – assylias

+0

NPE - Ich messe Speicher mit Windows Task Manager. –

Antwort

2

Ihr Code funktioniert gut. 380 MB Heap werden nach einem vollständigen GC tatsächlich verwendet.

Java ist bestrebt, Speicher zuzuweisen, um den GC-Overhead zu minimieren. Sie könnten die Größe des zugewiesenen Speichers begrenzen, indem Sie den Parameter -Xmx512m verwenden oder einen anderen GC - z. -XX:+UseConcMarkSweepGC oder durch -XX:MaxHeapFreeRatio=40.

+0

Vielen Dank für Ihre Antwort. Die große Zuweisung (viel davon aufgrund meines hohen -Xms-Werts) war ein Problem für mich, weil der GC nicht stattfand, so dass ich keine klare Vorstellung davon hatte, wie viel Speicher jeder Job tatsächlich benötigt. Ich muss eine Idee davon haben, weil ich noch in der Phase der Einrichtung von Workstations bin, und ich muss wissen, wie viel Speicher man jedem geben muss (jede Workstation wird 4 oder mehr Jobs auf einmal laufen lassen). –

1

Definieren Sie "Overhead". Die VM verwendet den zugewiesenen Heap, um zwischen Speicherbereinigungszeit und Ausführungsgeschwindigkeit zu balancieren (es gibt einige Schrauben, die Sie drehen können, um ihre Entscheidungen zu beeinflussen).

Die Norm ist die VM, die den Heap füllen lässt, bis der gc-Schwellenwert erreicht ist, dann sammeln, was auch immer Müll gesammelt werden kann, dann die Ausführung fortsetzen (das ist viel vereinfacht). Dies führt zu einem "Sägezahn" -Muster in der Heap-Nutzung (allmähliches Füllen, dann plötzlicher Abfall der Heap-Nutzung). Das ist völlig normal für Code, der Müll mit einer Rate erzeugt.

Die Punkte, die Sie beeinflussen können, sind wie hoch die "Zähne" bauen können (indem Sie den erlaubten Heap und/oder den GC einstellen). Die Geschwindigkeit der Garbage-Erstellung (wie stark die Heap-Nutzung ansteigt) hängt vom ausgeführten Code ab und kann von null bis zur maximal erreichbaren Allokationsrate reichen.

Ihr Lesecode ist vom Typ, um viele kleine Müllobjekte zu erstellen: die Zeile vom Scanner, die Teile, in die Sie die Zeile teilen. Wenn Ihr Heap groß genug ist, kann die gesamte Datei gelesen werden, ohne dass dieser Müll gesammelt wird (am wahrscheinlichsten ist dies bei Ihrer 4-GB-Heap-Einstellung der Fall).

Wenn Sie den Heap kleiner machen, wird die VM früher Müll sammeln, was die Speicherauslastung reduziert (ebenso können Sie mit den gc-Parametern spielen, um die Sammlung auf einem kleineren Prozentsatz des verwendeten Heaps zu erzwingen).

Es ist jedoch unvernünftig zu erwarten, dass der Code nur mit der Menge an Speicher ausgeführt wird, die Sie für Ihr Array berechnet haben. Was Sie im Task-Manager sehen, ist nur die Ansammlung des gesamten von der VM verwendeten Speichers. Dazu gehören Stapel, alle Ressourcen, die für die JRE benötigt werden, native Bibliotheken und der Heap.

Speicher außerhalb des Heapspeichers kann stark variieren, abhängig davon, wie viele Threads, Dateien und andere Ressourcen Ihr Programm verwendet. Als sehr grobe Faustregel gilt, dass mindestens 20-50 MB von der JRE selbst verwendet werden, auch wenn sie einfach nur so etwas wie eine "Hallo Welt" laufen lassen.Das Problem mit VM-Tuning, unabhängig davon, ob Sie nur die Heap-Größe oder die Feinabstimmung von GC-Parametern einstellen, besteht darin, dass es bei jeder Änderung des Problemsatzes erneut ausgeführt werden muss (zB könnten Sie mit -Xmx512m für Ihre aktuelle Datei durchkommen, aber Sie müssten den Wert für die nächste Datei anpassen).

Alternativ könnten Sie versuchen, die Menge an erstelltem Müll zu reduzieren, idealerweise auf Null. Anstatt den Scanner zeilenweise zu lesen, könnten Sie Zeichen für Zeichen lesen und die Analyse mit einer Zustandsmaschine durchführen. Dies wird erheblich reduzieren Müll Erstellung, aber machen Sie den Code viel komplexer. In vielen Fällen ist die "effizienteste" Lösung einfach keine Sorge über Speicherverbrauch - die Zeit, die für die Optimierung von VM-Parametern oder Code aufgewendet wurde, würde wahrscheinlich effizienter ausgegeben werden, wenn Sie sich auf den Fortschritt Ihres Programms konzentrieren. Solange "Overhead" dich nicht behindert, warum?

+0

Danke für diese gründliche Erklärung! Ein Teil des Problems für mich ist, dass ich gerade dabei bin, herauszufinden, wie viel Speicher ich an meinen Workstations benötige, und das machte eine genaue Bestimmung schwierig - ich habe auf jeder Seite 5 JCuda-Jobs gleichzeitig Workstation, so dass die nicht gesammelten 4 GB von jedem Job das Bild wirklich verwischten. –

+0

(Fortsetzung, da ich zu lange beim Bearbeiten gebraucht habe) ... Teil des Problems war auch, dass ich aus C++ kommend bin, um genau zu kontrollieren, wie viel Speicher meine Anwendung benutzt. Aber ich benutze Java, weil es für mich eine bessere Rapid-Prototyping-Sprache ist, zum Teil, weil ich Speicher nicht verwalten muss ... also werde ich mich daran gewöhnen. –

Verwandte Themen