Java Performance

Riker

Lieutenant
Registriert
Jan. 2005
Beiträge
862
Hallo Jungs!

Mein Umstand schilder sich wie folgt:
ich habe eine ORACLE-DB.
1. In diese lade ich aus 10 x *.csv-Files mit je 100.000 Rows in eine Tabelle "IMP" der DB.
2. Daraufhin werden diese soeben importierten Daten in eine andere Tabelle "BAS" übertragen. Dabei werden die Daten mit weiteren Informationen angereichert.

Soviel zum ungefähren Ablauf des gesamten Spaßes ;)

Jetzt das Problem:
der ganze Blödsinn braucht bei Schritt 2 anfangs ca. 8 Minuten und dann immer und immer länger... So dass es ab Dem 500.000sten Datensatz sein kann, dass dieser Übertrag von der Tabelle "IMP" nach "BAS" nicht mehr 8 Minuten, sondern 80 Minuten benötigt.

Die Frage nun:
kann ein System.out.println(); ein Flaschenhals werden? Ich lasse mir derzeit nämlich über diese Methode die aktuelle Zeile, in der sich das Programm befindet, ausgeben.


Danke und beste Grüße!
 
Eine Ausgabe in jeglicher Form ist immer ein Flaschenhals, weil dieser Befehl erstmal abgearbeitet werden muss, bevor er mit der eigentlichen Arbeit weiter machen kann. Die Ausgabe läuft ja nicht parallel zur eigentlich Datenverarbeitung. Da müsstest du schon mit Threads arbeiten.

Aber du kannst ja auch gern ein Test machen:
Code:
public class TestClass { 

  public static void main (String args[]) {

    for (int test = 0; test < 1000000; test++) {
      System.out.println(test);
    }

    System.out.println(test);

  }

}

Dieses Programm lässt du einmal so durchlaufen und beim zweiten Mal entfernst du die Zeile System.out.println(test);, die in der For-Schleife steht. Du wirst sehen, dass das Programm dann nur noch ein Bruchteil benötigt, wie beim ersten Durchlauf.

Um nicht ganz auf Ausgaben zu verzichten, würde ich dir vorschlagen nicht jeden Eintrag zu dokumentieren, sondern vielleicht jeden Tausendsten.
 
Zuletzt bearbeitet:
Riker schrieb:
Die Frage nun:
kann ein System.out.println(); ein Flaschenhals werden? Ich lasse mir derzeit nämlich über diese Methode die aktuelle Zeile, in der sich das Programm befindet, ausgeben.

Nein, scheint schon an der DB zu liegen. Wenn die Operationen mit steigender Datenmenge immer länger dauert, macht es Sinn einen Blick auf die Indizes zu werfen. Es kann ausserdem nicht schaden dir den Ausführungsplan anzeigen zu lassen.

Whiz-zarD schrieb:
Dieses Programm lässt du einmal so durchlaufen und beim zweiten Mal entfernst du die Zeile System.out.println(test);, die in der For-Schleife steht. Du wirst sehen, dass das Programm dann nur noch ein Bruchteil benötigt, wie beim ersten Durchlauf.

Ja, weil die Schleife dann wegoptimiert wird :p
 
Riker schrieb:
Jetzt das Problem:
der ganze Blödsinn braucht bei Schritt 2 anfangs ca. 8 Minuten und dann immer und immer länger... So dass es ab Dem 500.000sten Datensatz sein kann, dass dieser Übertrag von der Tabelle "IMP" nach "BAS" nicht mehr 8 Minuten, sondern 80 Minuten benötigt.

Die Frage nun:
kann ein System.out.println(); ein Flaschenhals werden? Ich lasse mir derzeit nämlich über diese Methode die aktuelle Zeile, in der sich das Programm befindet, ausgeben.

Da ich die genauen Details (Läuft das ganz z.b. in einer Transaktion?) nicht kenne hier ein paar generelle Tipps zum Verarbeiten solch großer Datenmenge:

1. Stell sicher, dass du die Daten über ein Batch-Insert einfügst. Das geht mit JDBC sehr gut. So wird nur ein SQL-Statement geschickt und dann werden die Daten nur noch gestreamt. Ohne Batch werde 1000de INSERTS geschickt, was Performance kostet

2. Gibt es Primärschlüssel? Wenn ja verwendest du vermutlich eine Sequenz für die Primärschlüsselgenerierung. Prüf bei dieser, ob der Cache-Wert hoch genug eingestellt ist. Wenn der Wert zu klein ist, dann führt das zu genau dem von Dir beschriebenen Problem

3. Ja, das System.out unbedingt unterlassen, wenn du Performance messen willst. Wenn es unbedingt sein muss, dann schreib nur für jeden 1000sten Datensatz eine Message, dann kannst du anhand der Zeitabstände auch gut sehen, wann es langsamer wird
Ergänzung ()

snow1 schrieb:
Nein, scheint schon an der DB zu liegen. Wenn die Operationen mit steigender Datenmenge immer länger dauert, macht es Sinn einen Blick auf die Indizes zu werfen. Es kann ausserdem nicht schaden dir den Ausführungsplan anzeigen zu lassen.

Ja, weil die Schleife dann wegoptimiert wird :p

Ja das könnte er theoretisch wegoptimieren, aber mach den Test mal mit folgendem Programm.

Code:
public class TestClass { 

    public static void main (String args[]) {

      int result = 0;
      long start = System.currentTimeMillis();
      for (int index = 0; index < 1000000; index++) {
        result += (index % 2 == 0 ?  index : -index);
        System.out.println(result); // einmal mit und einmal ohne ausgabe in schleife
      }
      long end = System.currentTimeMillis();
      System.out.println("Ergebnis : " + result  + "  Dauer: " + (end-start) + "ms");
    }


}

Hier kann die Schleife nicht wegoptimiert werden, weil result von der Schleife abhängt. Der Unterschied ist schon recht groß.
 
Banthor schrieb:
Ja das könnte er theoretisch wegoptimieren, aber mach den Test mal mit folgendem Programm.

Ja das ist mir auch klar :D Aber das sind nunmal zwei Paar Schuhe. In deinem Beispiel ist die lokale CPU der Flaschenhals. Bei dem DB Insert Programm ist die Festplatte auf dem entfernten DB-Server der Flaschenhals. Und da wird sich an der Performance dann nicht viel ändern, ob mit oder ohne Ausgabe. Er schreibt ja, dass es anfangs schnell geht und dann immer langsamer wird. Wahrscheinlich nicht, weil die Eclipse Console voll ist :lol:
 
snow1 schrieb:
Ja das ist mir auch klar :D Aber das sind nunmal zwei Paar Schuhe. In deinem Beispiel ist die lokale CPU der Flaschenhals. Bei dem DB Insert Programm ist die Festplatte auf dem entfernten DB-Server der Flaschenhals. Und da wird sich an der Performance dann nicht viel ändern, ob mit oder ohne Ausgabe. Er schreibt ja, dass es anfangs schnell geht und dann immer langsamer wird. Wahrscheinlich nicht, weil die Eclipse Console voll ist :lol:

Klar, die Ausgaben erklären nicht die 80Minuten :) Dennoch bremst soetwas ungemein. Bei mir dauert das Testprogramm mit Ausgabe knapp 10Sekunden - ohne Ausgabe dauert es 8Millisekunden, also ein erheblicher Unterschied bei einer primitiven Stringausgabe. Manche Logausgaben können jedoch besonders "schwer" sein, wenn z.B. großen Datenstrukturen durchlaufen werden nur um Statusmeldungen zu haben. In diesen Fällen können Logausgaben ein Programm ernsthaft ausbremsen.

In Bezug auf das Problem des TE glaube ich nicht, dass es an der Festplatte der entfernten DB liegt. In unserer Entwicklungsabteilung hatten wir ähnliche Problem wie der TE. Wir haben mehrer hunderttausend Datensätze eingefügt. Anfangs dauerte es über 10 Stunden und nach der Problemlösung nur noch <10 Minuten.

Zum einen lag es an einer schlechten Cache-Größe der eingesetzten Oracle-Sequence. Der Hauptgrund war allerdings, dass die die eingesetzte Persistenztechnologie (JPA mit Hibernate) äußerst ineffizient war.

@TE: Wenn du JPA (vor allem JPA) einsetzt, dann prüf mal ob es etwas bringt den First-Level-Cache zu flushen. Das Problem ist nämlich, dass dieser sich nach jedem Insert synchronisiert und je mehr Datensätze schon geschrieben wurden, desto länger dauert es.
 
Wie genau gehst du denn vor, wenn du in die Tabelle aus Schritt 2 schreibst? Steckst du die Zeilen in einen String und fügst sie dann ein? Strings können nämlich ein Programm stark ausbremsen, wenn sie zu lang werden. StringBuffer und -Builder funktionieren da besser.
 
@samotyr: Die Daten aus Schritt 1 lokal wieder zu holen um sie dann in Schritt 2 wieder einzufügen ist ohnehin Selbstmord. Egal ob mit String oder StringBuffer
 
Ich hatte neulich ähnliche Probleme beim parsen von ca. 300 MB XML Dateien und speichern der Werte in einer Rails Anwendung (mit MySQL DB). Da ich dort auf Duplikate der Einträge prüfen musste wurde das Eintragen neuer Werte auch mit der Zeit immer langsamer, zu einem großen Teil konnte ich das mithilfe geeigneter Indizes beheben. Vielleicht hilft das auch bei dir.
 
snow1 schrieb:
@samotyr: Die Daten aus Schritt 1 lokal wieder zu holen um sie dann in Schritt 2 wieder einzufügen ist ohnehin Selbstmord. Egal ob mit String oder StringBuffer

Ich wollte das auch nicht als Lösung vorschlagen. Vielmehr will ich darauf hinaus, dass man ohne Codeauszug im Dunkeln tappt und das gilt eigentlich für alles in diesem Thread. Die Vorschläge sind alle schön und gut, aber wenn der TE keinen Auszug postet, muss man Hellseher sein um den Fehler zu finden.

Und ich verstehe den TE so, dass er in Java aus einer Oracle-DB Daten importiert. Somit liegen die doch sowieso lokal vor, egal in welcher Datenstruktur, korrigier mich bitte wenn ich etwas übersehe.
 
Zuletzt bearbeitet:
Es wäre auch die Frage, ob man dieses "anreichern" anstatt in Java nicht auch direkt in SQL machen könnte (evtl. mit Hilfstabellen). Oder man lädt die Daten per Java erst in eine temporäre SQL-Tabelle (ohne Indizes) und dann von dieser um mittels etwas ala "Insert into bla (spalte1, spalte2) Select spalte1, spalte2 from temp".
Je nach Begebenheiten kann die Insertperformance auch durch Maßnahmen an der Datenbank optimiert werden: Temporäres Deaktivieren der Indexe und späteres Aufbauen, Deaktivieren des Transaktionslogs, Optimierung der Blockgrößen, Verwendung einer SSD etc.
 
Zuletzt bearbeitet:
An dieser Frage möcht eich andocken und gern wissen wieso du nicht pl/sql nimmst.
Bulk select z.b. sollten auch shcon in 10er gehen
 
Machst du ab und zu auch mal einen Zwischen-COMMIT?
 
AlbertLast schrieb:
An dieser Frage möcht eich andocken und gern wissen wieso du nicht pl/sql nimmst.
Bulk select z.b. sollten auch shcon in 10er gehen

Bin zwar nicht der TE, aber kann mir die Antwort als Java-Entwickler schon denken:

Welchen Vorteil bringt der Einsatz von PL/SQL gegenüber der Verwendung des Java-Oracle-JDBC-Treibers?

Ist der Vorteil so groß, dass man durch den Einsatz von PL/SLQ die Datenbankunabhängigkeit aufgibt?

Oder habe ich da was falsche verstanden?
 
kommt halt darauf wohin du willst,
wenn ums performance geht, worum es in diesen thread geht,
bist du mit pl/sql schneller mit der verarbeitung als mittels
einer java middelware lösung die das in die DB dann erst alles rein schaufeln muss.

Des weiteren kann man in Oracle eine csv als Read Only Tabelle einbinden und dann
mit normalen sql code daraus exportieren in andere Tabellen usw.
 
Zurück
Oben