Performance Probleme und Optimierungen in Java/J2EE Umgebungen (Teil 1)

Eine häufige Aussage von Anwendern ist: Schon wieder ist alles langsamer geworden. Oftmals nach neuen Releases, bei denen viele neue Features hinzugefügt und bestehende Funktionen erweitert wurden.

 

 

Weiterentwicklung entspricht einem Aufmotzen nach allen Regeln der Kunst, aber auch während der Entwicklung können sich die Anforderungen ändern. Durch diese Umstände konzentrieren sich viele Entwickler auf die eigentliche Business-Logik, die Anforderungen, nichtfunktionale Anforderungen wie Performance oder Design Patterns geraten eher in den Hintergrund.

Aber auch unbewusste Wiederverwendung kann zur allgemeinen Verlangsamung führen. Beispielsweise ist eine Komponente für das Versenden von Messages in eine Queue/Topic verantwortlich (InitiaContext, ConnectionFactory, Session initialisieren). Wird diese Komponente nun für den massenhaften Versand von Messages verwendet, so würde die Initialisierung zu Lasten der Performance gehen.

In meinen Projekten sind mir häufig Performance-Bottlenecks begegnet, die Top 10 davon sind:

 

  1. Nested Loops (stark verschachtelte Schleifen)
  2. Einzeldatensatz-Verarbeitung und Einzel-Datenlookups
  3. String-Verkettungen von langen Texten/Strings
  4. Redundante Initialisierung von Ressourcen
  5. Fehlende Indizes auf Datenbank-Ebene bzw. zu komplexe Datenbankabfragen
  6. Komplexe In-Memory Sortierung/Listenoperationen
  7. Falsche Nutzung von Komponenten
  8. Zu komplexe UI’s (selbstgebaute Performanceprobleme)
  9. Exzessive Speichernutzung (Out of Memory Errors)
  10. Unnötige Funktionalität

Nested Loops

Schleifen dienen dazu, Listen zu verarbeiten, irgendwelche Dinge zu Zählen und können sehr vielfältig eingesetzt werden. Hierarchisch aufgebaute Daten- und Programmstrukturen verleiten jedoch, Schleifen innerhalb von Schleifen zu verwenden. Die Iterationen innerhalb der Schleifen multiplizieren sich schnell in zeitraubende Prozeduren. Dabei muss oft nur ein kleiner Teil von Daten bearbeitet werden.

Mögliche Lösungen sind:

  • Anzahl der Schleifen verringern (z. B. nicht alle Hierarchiestufen durchlaufen, sondern die benötigten Daten per Bulk-Load in weniger Hierarchiestufen verarbeiten)
  • Anzahl der Daten (Iterationen) verringern

Einzeldatensatz-Verarbeitung und Einzel-Datenlookups

In der Regel werden einzelne Datenobjekte verarbeitet. Eine Verarbeitung in einer Schleife oder einen Batch kommt erst hinzu, wenn sich Anforderungen verändern. Aber auch Initial kann eine Programmstruktur entstehen, die z. B. für jeden Datensatz weitere Daten aus Ressourcen (meist Datenbank) abruft. Bei jedem Lookup entsteht ein Overhead, Queries müssen beispielsweise jedes mal von der Datenbank bearbeitet werden. Bei Datenbankabfragen dauert das Ermitteln der Daten oftmals länger als das Lesen (aus der Datendatei/Festplatte) und die Rückgabe der Daten.

Daher empfiehlt es sich, die Eingangsdaten in Blöcken zu staffeln (die Blockgröße ist abhängig von Datenvolumen, Parallelität usw. und muss in jedem Fall individuell bestimmt werden). Für diesen Block werden in Bulk-Abfragen (z. B. Select-Abfrage mit where xyz in (Liste von ID’s)) die erforderlichen Daten abgefragt und in einer Schleife zum passenden Datensatz in-memory sortiert. Besonders bei EJB Transaktionen macht sich diese Vorgehensweise bemerkbar. In einem meiner Projekte konnte ich allein durch die Blockgröße 10 eine Verarbeitung von 3 Stunden auf 10 Minuten reduzieren.

 

You may also enjoy…