Speed matters

JSF und Struts auf dem Leistungsprüfstand

Roman Schlömmer und Malte Geßner

Wenn die Performance einer Anwendung im Vordergrund steht, wie es in großen Projekten und speziell bei Hochlastsystemen der Fall ist, ist es für den Entwickler wichtig, vorher zu wissen, was auf ihn zukommt. Seit den Anfängen ist aber eine Menge Zeit vergangen, in der sich JSF weiterentwickelt hat. Grund genug, die aktuellen Versionen der am weitesten verbreiteten Implementierungen – die Referenz-Implementierung von Sun (RI) und Apache MyFaces (MyFaces) – auf den Prüfstand zu stellen. Komplettiert wird das Ensemble dabei von Struts, welches in unserem Szenario die Messlatte sein soll.

In diesem Artikel wollen wir erläutern, wie es um die Performance von JSF tatsächlich bestellt ist. Dabei geht es zunächst einmal um die harten Fakten und Zahlen. Zu diesem Zweck haben wir Testszenarien erstellt, die die Hauptargumente der Kritik aufgreifen. Die Recherche im Web hat ergeben, dass in früheren Versionen speziell ein komplexer Seitenaufbau mit vielen ineinander geschachtelten Komponenten und die Verwendung von großen Tabellen die Achillesferse von JSF dargestellt haben. Die persönliche Erfahrung hat außerdem gezeigt, dass auch das Speicheraufkommen bei der Verwendung von JSF höher als erwartet ausfiel. Somit sind große Tabellen, komplexe Komponentenbäume und der Speicherverbrauch die Aspekte, um die sich der Artikel hauptsächlich dreht. Wir wollen aber auch weniger harte Faktoren in die Diskussion mit aufnehmen, die letztlich das subjektive Empfinden des Anwenders berücksichtigen.

Testszenarien

Um mehr über die Aspekte zu erfahren, die die Performance von JSF bestimmen, ist ein Blick unter die Haube notwendig. Um ein an die Rich-Client-Entwicklung angelehntes Programmierparadigma zu realisieren, liegen die Oberflächen-Elemente nicht in flacher Form und voneinander unabhängig vor, sondern werden in einer der Anordnung entsprechenden Hierarchie vorgehalten. Im Zuge der Verwaltung dieses Komponentenbaumes kommt es zur Erzeugung, Änderung und Entfernung von einer Reihe von Objekten, die den Status des Baums und der einzelnen Oberflächen-Elemente abbilden. Ähnliches gilt auch für einzelne Komponenten, wie zum Beispiel Tabellen: Auch hier werden eine Reihe von Objekten verwaltet.

Unser erstes Testszenario wird also beleuchten, ob sich die Dauer eines Request-Response-Zyklusses linear zur Tiefe des Komponentenbaums verhält. Für diese Anordnung werden wir ebenfalls den Speicherverbrauch von RI und MyFaces beobachten und gegenüberstellen. Das zweite Testszenario setzt die Zeit, die die JSF-Implementierungen RI und MyFaces für die Darstellung von Tabellen unterschiedlicher Größe benötigen, mit der von Struts in Relation. Hier wollen wir einen Bearbeitungsroundtrip beobachten. Dabei werden Tabellen unterschiedlicher Größe erzeugt, deren Zellen Felder zum Bearbeiten der Daten enthalten. Ein Abschicken des Formulars führt dazu, dass die Daten an das Model gebunden werden und die Folgeseite die Daten samt den Änderungen anzeigt.

Das erste Testszenario …

… besteht aus zwei JSF-Webanwendungen, die sich nur in den unterschiedlichen JSF-Konfigurationen (web.xml und faces-config.xml) und der JSF-Implementierung unterscheiden. Jede JSF-Webanwendung besitzt eine Modell-Klasse Person, welche selbst über neun Properties verfügt. Diese neun Properties werden für die unterschiedlichen Breiten der Tabellendarstellung benötigt. Neben dieser Klasse gibt es JSF-typisch eine Managed-Bean-Klasse, PerformanceTestDepthBean. Diese Klasse besitzt eine Collection vom Typ java.util.List<Person> in der Property persons. In diesem Testszenario enthält die Collection zehn Einträge.

Um die unterschiedliche Performance messen zu können, existieren fünf JSP-Seiten, die einen unterschiedlich großen Komponentenbaum erzeugen. Für die Erzeugung der Komponentenbäume wird zunächst ein h:panelGrid generiert. Diesem Panel Grid wird nun eine h:dataTable-Komponente zugefügt, die ein ValueBinding zu persons besitzt und über neun Spalten-Definitionen alle Properties der Person ausgibt.

Um nun einen tief geschachtelten Komponentenbaum zu erzeugen, enthält das Panel Grid neben der Datentabelle noch ein weiteres h:panelGrid, welches wiederum eine h:dataTable-Komponente enthält und die persons ausgibt. Für die Messungen haben wir in den fünf zuvor erwähnten JSP-Seiten unterschiedlich tief geschachtelte Komponentenbäume erzeugt. Es gibt je eine JSP-Seite mit einer Tiefe von zwei, vier, sechs, acht und zehn. Dies bedeutet, dass bei einer Tiefe von acht es acht ineinander geschaltete h:panelGrid-Komponenten gibt, die jeweils eine h:dataTable-Komponente besitzen. Diese Anordnung von Tabellen mit immer gleichem Inhalt ist fachlich natürlich nicht wirklich sinnvoll. Wir haben dieses Vorgehen aber bewusst gewählt, weil wir lediglich die Auswirkung der Tiefe des Komponentenbaums betrachten wollen, unabhängig von den konkreten Komponenten im Baum.

Für die Messung haben wir JMeter verwendet. Bei der Messung wurde die gesamte Dauer für einen Request-Response-Zyklus betrachtet. Ausgeschlossen wurde dabei die Zeit für das eigentliche Rendern der Anzeige im Browser und das bandbreitenabhängige Übertragen der Daten. Die Tests haben ausschließlich lokal stattgefunden und sind somit auch von Netzwerkengpässen unabhängig. Wir haben für die unterschiedliche Tiefe des Komponentenbaumes jeweils einen Testfall aufgestellt. Um die reine Performance eines Zyklusses zu messen, wurden alle Requests sequentiell abgearbeitet, und für einen aussagekräftigen Durchschnittswert haben wir gesorgt, indem wir 1000 Requests pro Testfall abgesetzt haben. Um den Speicherbedarf einer HTTP-Session ermitteln zu können, haben wir im Tomcat die Context-Konfiguration für die beiden Anwendungen erweitert.

Es gibt die Möglichkeit, über das Manager-Element innerhalb eines Contexts zu definieren, wie der Tomcat mit Sessions umgehen soll. Für die Messung einer HTTP-Session bot sich hier der org.apache.catalina.session.PersistentManager an. Dieser serialisiert eine HTTP-Session beim Shutdown des Servers auf die Festplatte. Über die erzeugte Datei lässt sich dann in etwa die verbrauchte Größe ermitteln. Hier die Konfiguration für den PersistenceManager:

Geschrieben von
Roman Schlömmer und Malte Geßner
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.