BlackBoard (http://www.black-board.net/index.php)
- Design, Programmierung & Entwicklung (http://www.black-board.net/board.php?boardid=55)
-- Programmieren (http://www.black-board.net/board.php?boardid=4)
--- Java Datei Einlesen, performant? (http://www.black-board.net/thread.php?threadid=23275)


Geschrieben von Antiheld am 28.03.2008 um 02:46:

Fragezeichen Datei Einlesen, performant?

Hallo,

ich habe mal eine Verständnis Frage. Gegeben ist folgender Code:
code:
1:
2:
3:
4:
5:
BufferedReader inputStream = new BufferedReader(new FileReader(fileName));	
String line = "";
while((line = inputStream.readLine()) != null){
	text += line +"\n";
}
Der Sinn ist klar, es wird eine Datei zeilenweise eingelesen. An sich ist damit auch alles vollkommen in Ordnung und bei normalen, sprich kleinen, Textdateien ist die Zeit dafür vernachlässigbar. Jetzt allerdings staune ich das es bei einer großen Datei, hier konkret 4k Zeilen, fast 10 Sekunden dauert. Das es länger dauert ist ja logisch, aber 10 Sekunden? Es ist hier nicht wirklich tragisch, das Programm dahinter ist nur zum einmaligen Gebraucht, jedoch interessiert mich das gerade.

Irgendjemand eine Idee?

Grüße
Anti



Geschrieben von Black Star am 28.03.2008 um 09:25:

 

Willkommen in der Welt von Java Augenzwinkern

Ich will jetzt nicht unsachlich staenkern, aber Java und Performance schliessen sich gegenseitig weitestgehend aus.
Ich weiss auch wovon ich rede, wir haben in unserer Gruppe einige Java-Programme laufen und vergleichbarer C/C++ Code ist immer schneller und oftmals viel schneller.

Das Problem in deinem Fall konkret koennte sein, dass der string 40k mal erweitert werden muss, d.h. neuer Speicher allokiert werden muss, ...
Wie das konkret funktioniert kann ich dir nicht sagen, aber es ist nicht ausgeschlossen, dass wirklich bei jedem += neuer Speicher gesucht werden muss, was gerade bei Java ewig dauert.
Vielleicht versuchst du den String vorher auf die Groesse der Datei zu bringen und schreibst dann Zeichen fuer Zeichen in die Elemente des Strings? Der Vorteil waere, dass du nur einmal Speicher allokieren musst.



Geschrieben von PygoscelisPapua am 28.03.2008 um 09:39:

 

I/O und Java ist schon immer ein schwieriges Thema gewesen, und Java tut sich hier sehr schwer (es gab mal eine Zeit da hätte ich sogar erklären können warum *seufsz*).

Die Lösung Deines Problems ist allerdings folgende:
Du nutzt den BufferedReader in der "Standardausführung" - d.h. der BufferedReader hat eine bestimmte größe (die genaue Größe weiß ich jetzt allerdings aus dem Kopf auch nicht mehr - ich glaube irgendwas um die 8.000 Bytes).

Sollte Deine Textdatei größer sein, dann kommt Java damit ins schwitzen (warum genau, kann ich Dir nicht mehr erklären - wenn es Dir wirklich wichtig ist, kann ich aber mal am WE in meinen Studienunterlagen suchen, ob ich es wiederfinde).

Das umgehen kannst Du, in dem Du die Buffer-Größe individuell auf Deine Bedürfnisse setzt. Das geht so:

code:
1:
2:
BufferedReader(FileReader, int );

Wobei int die Größe des Buffer ist (ich meine in Byte).

Hoffe, das hilft weiter.



Geschrieben von Antiheld am 28.03.2008 um 10:55:

 

Ich habe das Problem jetzt gelöst, es war ungefähr das was Black Star meinte.
code:
1:
2:
3:
4:
5:
6:
BufferedReader inputStream = new BufferedReader(new FileReader(fileName));	
String line = "";
StringBuffer text = new StringBuffer();
while((line = inputStream.readLine()) != null){
	text.append(line +"\n");
}
Somit braucht das Programm nur noch 1/6 der ursprünglichen Zeit.

Ich hab es auch mit
code:
1:
BufferedReader(Reader in, int sz) 
versucht. Leider gibt es in der Java Dokumentation zu BufferedReader keine wirkliche Auskunft darüber welche Dimension sz haben soll. Mit sz = 500000 (Datei ist 484893 Bytes groß) dauerte es zumindest noch länger. Das hat mir dann gesagt der String mit der allokierung in solchem maß nicht klar kommt und hab's mit dem Stringbuffer versucht.

Danke euch beiden smile



Geschrieben von phlox81 am 28.03.2008 um 11:24:

 

Zitat:
Original von Black Star
Willkommen in der Welt von Java Augenzwinkern

Ich will jetzt nicht unsachlich staenkern, aber Java und Performance schliessen sich gegenseitig weitestgehend aus.
Ich weiss auch wovon ich rede, wir haben in unserer Gruppe einige Java-Programme laufen und vergleichbarer C/C++ Code ist immer schneller und oftmals viel schneller.


Mag ja genügend Beispiele geben, wo dem so ist, aber Java ist in der modernen Version durchaus nahe an C++ herangekommen. Gerade bei einer solchen Einleseoperation, sollte der Unterschied nicht allzu groß sein.

Denke das Problem liegt hier eher am "Algorithmus". Du ließt die Datei Zeilenweise aus, brauchst sie aber eigentlich am Stück. Auch in C++ würde das was dauern, weil du ja wie Black Star schon bemerkte, jedesmal neuen Speicher allokierst.
Von daher würde ich die Datei in einem Rutsch auslesen, oder sie Zeilenweise dann in einen Vector/DynArray schreiben.

phlox



Geschrieben von Antiheld am 28.03.2008 um 12:12:

 

Ich hab's nochmal umgeschrieben, jetzt lese ich alles in einem Rutsch aus. Der Grund warum ich diese zeilenweise Lösung hatte lag darin das ich früher einen ASCII String erhalten hatte, obwohl Unicode gefordert ist. Habe jetzt nochmal genauer nachgeschaut und das Problem jetzt auch beseitigt.

Ich freu mich immer wieder dabei was neues zu lernen großes Grinsen

Grüße
Anti



Geschrieben von PygoscelisPapua am 28.03.2008 um 12:35:

 

Zitat:
Original von Black Star
Ich weiss auch wovon ich rede, wir haben in unserer Gruppe einige Java-Programme laufen und vergleichbarer C/C++ Code ist immer schneller und oftmals viel schneller.


Ohne dass jetzt hier in einem Java-Flamewar ausarten lassen zu wollen:

Das ist ja schön und gut was Du da sagst, und sicherlich auch richtig. Nur sollte nicht vergessen werden, dass die Anwendungsgebiete, und die Ansprüche, die die Sprachentwickler an die Sprache gestellt haben, bei den beiden Sprachen doch unterschiedlich sind, und man je nach Anwendung daher auch nicht die Wahlfreiheit hat!

Daher sind solche "C/C++ kann das besser" Kommentare meistens für die Katz (besonders ärgerlich ist sowas, wenn man das in der Uni als Vorgabe hat, und irgendwo Hilfe braucht und erstmal von 2.000 Leuten gesagt bekommt, man solle doch C++ nutzen Augen rollen ).



Geschrieben von Black Star am 29.03.2008 um 12:46:

 

Zitat:
Original von phlox81
Gerade bei einer solchen Einleseoperation, sollte der Unterschied nicht allzu groß sein.


Gerade dabei ist Java langsam. Jedes eingelesene Ascii-Zeichen muss erst zweimal konvertiert werden. Einmal nach Big Endian und dann nach UTF-8 (oder umgekehrt, keine Ahnung).
Dazu kommt in dem Fall, dass mehr als nur ein paar kilobyte Speicher allokiert werden muessen, was auch unwahrscheinlich viel Zeit kostet unter Java. Wenn haeufig oder wenn viel Speicher allokiert werden muss, ist Java immer noch um einen Faktor 100 bis 1000 langsamer als vergleichbare C/C++ Programme.

Ich habe mal ein kleines c++ Programm geschrieben, dass das gleiche macht.

code:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

std::stringstream buffer;               //!< string buffer (line by line)
std::vector<char> binBuffer;            //!< binary buffer

/*!
 * Line-by-line read -- adding to stringbuffer
 */
void bufferedReader( char* fileName )
{
        std::ifstream file( fileName );
        std::string line;
        while ( ! file.eof() )
        {
                std::getline( file, line );
                buffer << line << std::endl;
        }
        file.close();
}

/*!
 * Binary-at-once read -- adding to STL vector
 */
void binReader( char* fileName )
{
        std::ifstream binFile( fileName, std::ios::binary | std::ios::in );

        binFile.seekg( 0, std::ios::end );
        size_t binSize = binFile.tellg();
        binFile.seekg( 0, std::ios::beg );

        size_t buffPos = binBuffer.size();
        binBuffer.resize( buffPos + binSize );

        binFile.read( &(binBuffer[buffPos]), binSize );

        binFile.close();
}

/*!
 * argv[1] -> filename; argv[2] -> "1": stringstream, "2": binary STL vec
 */
int main( int, char** argv )
{
        if ( std::string( argv[2] ) == "1" )
        {
                for ( int i = 0; i < 100; ++i )
                {
                        bufferedReader( argv[1] );
                }
                std::cout << "[MAIN] total bytes added to buffer: " << buffer.str().length() << std::endl;
        }
        else if ( std::string( argv[2] ) == "2" )
        {
                for ( int i = 0; i < 100; ++i )
                {
                        binReader( argv[1] );
                }
                std::cout << "[MAIN] total bytes added to buffer: " << binBuffer.size() << std::endl;
        }

        return 0;
}

Wie man sieht, muss ich die Einleseoperationen wenigsten 100 mal ausfuehren, damit ich sinnvoll eine Zeit messen kann.
Die einzulesende Datei ist der Quellcode eines Gittergenerators, die groesste Textdatei, die ich gerade finden konnte:
code:
1:
2:
$ wc ~/NLD/src/libnldtools/extern/tetgenio/tetgen.cxx
  34961  151648 1278017 /home/ulli/NLD/src/libnldtools/extern/tetgenio/tetgen.cxx
Das heisst es werden insgesammt 127801700byte in den Puffer geladen, ohne dass er zwischendurch geleert wird.
Ausserdem habe ich das Programm mit "-O0" kompiliert, damit der Kompiler nicht optimiert und der Code wirklich so ausgefuehrt wird.

Hier die Ergebnisse:
String buffer:
code:
1:
2:
3:
4:
5:
6:
$ time ./readfile /home/ulli/NLD/src/libnldtools/extern/tetgenio/tetgen.cxx 1
[MAIN] total bytes added to buffer: 127801800

real    0m9.885s
user    0m6.060s
sys     0m1.728s
STL vector / binaer:
code:
1:
2:
3:
4:
5:
6:
$ time ./readfile /home/ulli/NLD/src/libnldtools/extern/tetgenio/tetgen.cxx 2
[MAIN] total bytes added to buffer: 127801700

real    0m5.802s
user    0m1.803s
sys     0m1.812s
Und das auf einem 800MHz Duron mit 256MB Arbeistspeicher, womit die einzuladenen ~120MB schon weh tun.

Aus eigener Erfahrung weiss ich, dass das unter Java vollkommen anders aussaehe.
Ich habe es jetzt nicht ausprobiert, aber Antiheld hat ja schon bemerkt, dass eine einzige Einleseoperation so lange dauert, wie bei mir 100.

Das soll jetzt auch nicht als Aufruf oder Beitrag zum Flamewar verstanden werden, sondern ist nur eine simple Feststellung.
Wir hatten Antiheld ja bereits geholfen, und zum rumspielen und lernen ist Java ja auch ganz in Ordnung.



Geschrieben von phlox81 am 29.03.2008 um 14:35:

 

Zitat:
Original von Black Star
Zitat:
Original von phlox81
Gerade bei einer solchen Einleseoperation, sollte der Unterschied nicht allzu groß sein.


Gerade dabei ist Java langsam. Jedes eingelesene Ascii-Zeichen muss erst zweimal konvertiert werden. Einmal nach Big Endian und dann nach UTF-8 (oder umgekehrt, keine Ahnung).
Dazu kommt in dem Fall, dass mehr als nur ein paar kilobyte Speicher allokiert werden muessen, was auch unwahrscheinlich viel Zeit kostet unter Java. Wenn haeufig oder wenn viel Speicher allokiert werden muss, ist Java immer noch um einen Faktor 100 bis 1000 langsamer als vergleichbare C/C++ Programme.



Gerade hier solltest du einen modernen GC nicht unterschätzen. Aber natürlich ist C++ hier immer etwas schneller, da es das ja direkt machen kann.
Trotzdem sollte man nicht den Fehler begehen, und als C++ Programmierer versuchen ein entsprechendes Java Programm gleich gut implementieren (in Java!) zu wollen.
Dafür fehlt einem einfach das Hintergrundwissen häufig. Zu mal man auch Java noch optimieren kann.

phlox



Geschrieben von PygoscelisPapua am 29.03.2008 um 15:11:

 

Wie ich oben schon mal geschrieben habe, ist das, wie wenn man Äpfel mit Birnen vergleicht.

Schreib doch mal ein Applett oder ein Servlett in C++. Schreib doch mal ein Programm in C++, dass Hardware- und Betriebssystemunabhängig auf allen Rechnern, ja sogar auf Handy, PDAs, MDAs, Toastern oder Mikrowellen läuft.

Wenn das C++ genau so wie Java hinbekommt - dann können wir uns darüber unterhalten, ob C++ besser als Java ist, oder nicht, weil es Performanter ist, etc.

Solange das nicht der Fall ist, kann man mit Deiner Argumentation vielleicht jemanden eine Freude machen, der einen Windowstreiber in Java schreiben möchte -- der hat dann aber auch keine Ahnung über die Einsatzswecke von Java und C und C++.
Keine Freude machst Du damit allerdings Leuten, die mit Java ein Programm schreiben wollen, und dieses in Java performanter machen wollen (denn in der Regel ist dann ja auch davon aus zu gehen, dass diese Leute wissen, warum sie Java einsezten - und zumindest bei Antiheld nehme ich das auch an)...



Geschrieben von phlox81 am 29.03.2008 um 15:20:

 

Zitat:
Original von PygoscelisPapua
Wie ich oben schon mal geschrieben habe, ist das, wie wenn man Äpfel mit Birnen vergleicht.

Schreib doch mal ein Applett oder ein Servlett in C++. Schreib doch mal ein Programm in C++, dass Hardware- und Betriebssystemunabhängig auf allen Rechnern, ja sogar auf Handy, PDAs, MDAs, Toastern oder Mikrowellen läuft.

Sowas ließe sich durchaus auch in C++ um setzen, es gibt sogar kleinere nicht so bekannte AppServer für C++. Z.B. tntnet.

Und noch etwas: C++ ist keine einheitliche Plattform, wie es Java vorgibt zu sein.
Java braucht immer seine Laufzeitumgebung, welche selber nicht in Java implementiert ist.

http://bash.org/?338364

Zitat:

Wenn das C++ genau so wie Java hinbekommt - dann können wir uns darüber unterhalten, ob C++ besser als Java ist, oder nicht, weil es Performanter ist, etc.


Ich mag diese Java vs. C++ vs. .net Flames nicht. Ignorieren sie doch völlig, das sie aus einer Zeit stammen in der der Softwaremarkt noch die Größe eines Fischteiches hatte. Mittlerweile ist aber daraus ein riesen Ozean geworden, und da ist nun mal platz für Vielfalt, weshalb es auch in Zukunft eher mehr als weniger Programmiersprachen geben wird.

Und wann gibts eigentlich mal ne ISO Norm für Java? großes Grinsen

phlox


Forensoftware: Burning Board 2.3.6, entwickelt von WoltLab GmbH