java.io.BufferedReader == Flaschenhals?

miffi

Erfahrenes Mitglied
Hallo zusammen.

Momentan muss ich Log-Dateien parsen, es handelt sich dabei um große Text-Dateien mit mindestens mehreren hundert MB (mein Testfile ist ~400MB). Da ich jede Zeile einzeln brauche, hat sich der java.io.BufferedReader angeboten, der mir bequemerweise bereits eine readLine() Methode liefert.

Das Problem ist jetzt, dass das Einlesen der Datei viel zu lang dauert. Ich habe alle Operationen auf die eingelesenen Zeilen auskommentiert, um wirklich die reine Lesedauer zu erhalten. Die Messung sieht folgendermaßen aus:
Java:
				FileReader fr = new FileReader(file);
				br = new BufferedReader(fr);
				
				String line = null;

				long lineCounter = 0;
				long ts1 = System.currentTimeMillis();
				
				while((line = br.readLine()) != null){
// hier kämen Aufrufe zum Verarbeiten der Zeile				
					
					lineCounter++;
					if(lineCounter % 1000 == 0){
						long ts2 = System.currentTimeMillis();
						System.out.println(lineCounter + " (" + (ts2 - ts1) + "ms)");
						ts1 = ts2;
					}
					Thread.sleep(1);
				}
Diese Messung ergibt ca. 1ms/Zeile Verarbeitungszeit. Was auch zutrifft, wenn ich ein kleines Testfile benutze oder den Puffer des Readers höhersetze (Maximum waren 10k bisher).

Meine bisherigen Überlegungen/Ansätze/Tests:
  1. An mangelnden System-Resourcen/hohem Garbage-Collecting kann es kaum liegen, da stets zwischen 1 und 2 GB RAM zur Verfügung standen, sowie 90% CPU (2x2.3GHz). Der Javaw-Prozess hat auch nie mehr 10 - 12 KB RAM in Anspruch genommen.
  2. Andere Streams zu verschachteln (FileInputStream => BufferedInputStream => InputStreamReader => BufferedReader) hat keinen Unterschied gemacht
  3. RandomAccessFile#readLine() dauert ebenfalls gleich lang
  4. Mit einer Puffergröße von 10k komme ich beim byte-weisen Einlesen auf 0.0068ms/Zeile. Das ist jedoch hinfällig, wenn die Daten programmatisch wieder nach CR/LF o.Ä. durchsucht und gesplitted werden müssen (ebenfalls 1ms/Zeile).
  5. Ein Kollege hat eine kleine (und schlampige) Test-Routine in C# geschrieben, mit einem BufferedReader-Äquivalent und kam (inkl. kleiner Stringverarbeitung) auf 0.038ms/Zeile, was immerhin um einen Faktor 25+ schneller ist als mein Auslesen.

Ich hoffe sehr, dass Java in Sachen Character-Verarbeitung nicht einfach nur langsam ist und einer von euch mir weiterhelfen kann. Mein guter Freund Google sagt mir seit gestern, dass es (außer Frameworks von Apache z.B.) keine besseren Ansätze gibt zum zeilenweisen Auslesen als den BufferedReader.

Grüße
miffi
 
Zuletzt bearbeitet von einem Moderator:
Hi,
also ich habe auch die Zeit vom BufferedReader gestoppt und komme auf weniger.
Ersteinmal meine PC-Daten:
CPU: Intel Core 2 Duo 2,80 GHz
RAM: 2GB

Ich komme bei einem Textdatei mit einer Größe von 445MB auf eine Lesegeschwindigkeit von 0,0027 ms/Line. Die Textdatei enthält 5millionen Zeilen. Für die gesamte Textdatei benötige ich eine Zeit von 13500ms oder anders 13,5sec.

Hier mein Code mit dem ich getestet habe.
Java:
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("./Tests/bigfile.txt"))));
			String line;
			StopWatch sw = new StopWatch();
			sw.start();
			int lineCounter = 0;
			while((line = reader.readLine()) != null) {
				lineCounter++;
			}
			sw.stop();
			System.out.println("NeededTime=" + sw.getTime() + ", Lines="+lineCounter+", ms/Line="+((double)sw.getTime()/(double)lineCounter));
		} catch(FileNotFoundException e) {
			e.printStackTrace();
		} catch(IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(reader);
		}
	}

Du solltest bei deinem Code darauf achten, bei einer Zeitmessung kein Thread.sleep(...) einzubauen. Das ist dann klar, dass wen du den Thread für 1msec schlafen legst, dass er dann pro Zeile ca. 1msec braucht. ;)
Außerdem verlangsamt eine Ausgabe die Zeitmessung zusätzlich, genauso wie sonstige Berechnung, Bedingungen, etc.

Gruß

Fabio
 
Hi Fabio, danke für die schnelle Antwort!

Der sleep()-call war ein Relikt aus einer anderer Schleife, die ich an dieser Stelle hatte. Und vor lauter Betriebsblindheit hab ich es total übersehen...
Ich geh jetzt in 6.7s über 6.2 Mio Zeilen Text, von daher ist alles in Ordnung. Weißt, da programmiert man seit Jahren professionell, und dann passiert einem so ein Erstsemester-Kram xD
Wär ja direkt lustig, wenn nicht 8 wichtige Arbeitsstunden für die Forschung nach Alternativen draufgegangen wären...

Danke dir und Gruß
miffi
 
Keine Ursache. Oft hilft auch gelegentlich eine kurze Pause von 5 Minuten, um seine Blindheit zu überwinden. ;)
Aber ich kenn das auch, die kleinsten Fehler sucht man prinzipell immer am längsten.

Gruß

Fabio
 
Zurück