Parallele parsen

Hallo,

ich habe ein Speicher und Performanzproblem beim parsen langer Texdateien (>200 MB).

* ich benutze NIO statt IO zum Einlesen der Datei
* Einlesen in den Speicher kommt aufgrund der Dateigröße nicht in Frage und der JVM mehr Speicher zuweisen ist auch keine Lösung => nutze BufferedReader, um nicht OutofMemory zu laufen.

-> Ich lese also Zeilenweise ein und frage mich, ob ich nicht das „Zeilenweise-parsen“ parallelisieren kann.

Jetzt zu meiner eigentlichen Frage:

Wie kann ich das Auslesen eines BufferedReaders parallelisieren. Ist das überhaupt möglich?
Das Paralellisieren des Parsers ist kein Problem, jedoch können die einzelnen Threads nur so schnell sein wie der BufferedReader neue Zeilen liefert. Daher würde ich auch gerne das Auslesen der Zeilen parallelisieren; oder bringt das bei einigen Millionen Zeilen keinen Geschwindigkeitsvorteil?
Die Reihenfolge der Zeilen ist mir egal, da es auf den Inhalt der Zeile ankommt. (-> über ein Codefragment würde ich mich besonders freuen:smile:

Hier mal ein Auszug des Einlesens und welcher Block parallel bearbeitet werden soll:

public int readFile(Path file){
Charset cs = StandardCharsets.ISO_8859_1;
try {
BufferedReader reader = Files.newBufferedReader(file, cs);
String zeile = null;

//-> gesamter while-Block soll parallelisiert werden
while ((zeile = reader.readLine()) != null)
{

ZeilenParser.pars(zeile)
}
} catch (IOException e) {
//„Fehler beim Öffnen oder Lesen der Datei!“
e.printStackTrace();
}
}

Danke und Gruß

Sebastian_

Hi,

zum Parallelisieren habe ich auf Anhieb keine Idee, aber wie wäre es wenn du regelmäßig von Hand den garbagecollector leerst (System.gc(); müsste das sein )

Grüße

edit
Hast du dir die JVM Optionen schon mal angeguckt?

Da kann man auch viel einstellen bspw:

-XX:+UseParallelGC -XX:stuck_out_tongue:arallelGCThreads=20

es gibt auch einen Parameter der Einstellt wie oft der GC geleert werden soll - oder auch die ServerVM kann u.U. auch zur besseren Performance beitragen

Ich hoffe ich konnte dir helfen

1 Like

Hallo,

ich habe mal etwas herumgespielt, da das reine Auslesen von 50 Mio Zeilen (was ca. 1.3 GB waren) ca. 10 Sekunden gedauert hat, vermute ich das Parsen ist das was dir die Performance zunichte macht.

Ich habe mal beides versucht, paralleles Lesen war zumindest mit meinem Ansatz nicht erfolgsversprechend weil scheinbar das „skippen“ der nicht gelesenen Zeilen ähnlich lange dauert wie das lesen selbst.

Das parallele Parsen zeigt jedoch deutliche Performanceverbesserung schon bei relativ wenig Zeilen (der Output wurde mit 5000 erstellt), wenn das Parsen eine gewisse Zeit in Anspruch nimmt (im Beispiel ein Sleep mit 2ms):

D:\temp\>java WWWTest parallelParsing
Active Threads: 16
 finished in 1085 ms

D:\temp\>java WWWTest
Active Threads: 4
Reader\_2: 1620 lines read in 3268 ms
Active Threads: 3
Reader\_0: 1792 lines read in 3612 ms
Active Threads: 2
Reader\_1: 3286 lines read in 6634 ms
Active Threads: 1
 finished in 7025 ms

Ich hoffe mein Quick ‚n‘ Dirty Beispiel lässt sich auf deinen Anwendungsfall übertragen.

Gruß
Heavy

import java.io.\*;

public class WWWTest
{
 private static final File bigFile = new File("bigFile.txt");
 // private static final int max = 50000000;
 private static final int max = 5000;

 public static void main(String[] args) throws IOException
 {
 if(args.length \> 0 && args[0].equals("createFile"))
 createFile();
 else if(args.length \> 0 && args[0].equals("parallelParsing"))
 parallelParsing();
 else
 readFile();


 }

 private static class MyReader extends BufferedReader
 {
 private long count = 0;
 private long max;

 public MyReader(Reader reader, long skip, long max) throws IOException
 {
 super(reader);
 skip(skip);
 this.max = max;
 }

 @Override
 public String readLine() throws IOException
 {
 String line = super.readLine();
 if(line != null)
 count += line.length();

 return count 1)
 {
 try
 {
 Thread.sleep(500);
 System.out.print("\rActive Threads: " + Thread.activeCount());
 }
 catch(InterruptedException e) {}
 }
 System.out.println("\n finished in " + (System.currentTimeMillis() - start) + " ms");
 }

 private static void readFile() throws IOException
 {
 long start = System.currentTimeMillis();
 long skip = 23 \* (max / 3);
 BufferedReader reader1 = new MyReader(new FileReader(bigFile), 0, skip);
 BufferedReader reader2 = new MyReader(new FileReader(bigFile), skip, (2\*skip));
 BufferedReader reader3 = new MyReader(new FileReader(bigFile), (2\*skip), Long.MAX\_VALUE); 
 BufferedReader[] readers = {reader1, reader2, reader3};

 int tCount = 0;
 for(final BufferedReader reader : readers)
 {
 Thread thread = new Thread("Reader\_" + (tCount++)) {

 public void run()
 {
 try
 {
 long tStart = System.currentTimeMillis();
 MyParser parser = new MyParser();
 int count = 0;
 String line = null;
 while((line = reader.readLine()) != null)
 {
 count++;
 parser.parseLine(line);
 } 
 System.out.println("\n" + getName() + ": " + count + " lines read in " + (System.currentTimeMillis() - tStart) + " ms");
 }
 catch(IOException e)
 {
 System.err.println(e.getMessage());
 }
 }
 };
 thread.start();
 }

 while(Thread.activeCount() \> 1)
 {
 try
 {
 Thread.sleep(500);
 System.out.print("\rActive Threads: " + Thread.activeCount());
 }
 catch(InterruptedException e) {}
 }
 System.out.println("\n finished in " + (System.currentTimeMillis() - start) + " ms");
 }

 private static void createFile() throws IOException
 {
 FileWriter writer = new FileWriter(bigFile);
 for(int i=0; i

Supi, danke für die schnelle Antwort.

Werde ich dieses Wochenende gleich mal austesten…und sieht für Quick’n Dirty gar nicht mal so schlecht aus :wink:

Merkwürdig, dass „skippen“ genauso lange dauert wie lesen, aber bei Java lauern ja einige Fallstricke…

(wobei viele Performanz und Java gleich ausschließen - man muss sich bei der Geschwindigkeit und Speicherverbrauch halt etwas mehr Mühe geben -> dann ist es meist schnell genug :wink:

Viele Grüße

Sebastian_

Hallo jo!

Danke für deine Antwort.

Habe mich die ganze Zeit nur auf das Parallelisieren vom Quellcode beschränkt, aber werde sicherlich durch deinen Tipp eventuell noch ein bisschen mehr rausholen können.

-> Da hätte ich ja auch mal selber drauf kommen können, aber manchmal sieht man den Wald vor lauter Bäumen nicht mehr…

Werde mal bei Oracle schauen was die VM-Parameter so alles hergeben. Bis jetzt habe ich nur die VM Parameter zum Aufbohren des geringen Speichers der VM benötigt :wink:

Man lernt eben nie aus,

VG Sebastian_