Performanceoptimierung

10 Kniffe um die Performance deiner Anwendung zu verbessern! – Teil 1

Ist die Performance deiner SAPUI5 Anwendung zu langsam? Diese Frage hat sich bestimmt jeder schon mal gestellt. Zu meist kommt einem aber nicht die Frage als erstes in den Sinn sondern die Antwort. Und meist auch nur dann wenn gilt:

Meine Anwendung ist zu langsam!

Im Folgenden möchte ich 10 Gründe auflisten, die die Performance einer Anwendung negativ beeinflussen könnten. Oder, um das ganze positiv zu formulieren, 10 Kniffe, mit denen du deine Anwendung merklich beschleunigen kannst. So könnte aus deinem Trabbi ganz einfach ein Ferrari werden! Naja, vielleicht manchmal auch nur ein Golf, aber vielleicht reicht ja auch schon der Schritt dahin?

Wer von euch ein paar meiner anderen Beiträge gelesen hat, hat sicher festgestellt, dass ich des Öfteren schon die Performance erwähnt habe. Und das auch aus gutem Grund: Die Performance ist eines der entscheidenden Argumente wenn es darum geht die Akzeptanz der Anwendung bei einem Enduser zu steigern bzw. überhaupt zu erreichen.
Aber lange Rede kurzer Sinn. Hier nun die ersten 5 Kniffe um die Performance deiner Anwendung zu verbessern:

Kniff 1 – Ausschalten des count-Mode im ODataModel

Der count-Mode ist eine standardmäßig gesetzte Eigenschaft bei einem ODataModel. Diese führt dazu, dass zusätzlich zu dem Backend-Call, der die Daten an das Frontend liefert, ein weiterer Call gemacht wird, der vorweg die Anzahl der Einträge zählt, die vom Backend geliefert werden.
Mein Kollege Sebastian Garms hat das Thema in seinem Blog-Artikel schon gründlich beschrieben, deshalb lasse ich es an dieser Stelle mit der kurzen Beschreibung gut. Weil es aber so einfach ist und der Effekt so groß empfehle ich euch aber dringlichst, dort mal reinzuschauen. Der Beitrag zeigt nicht nur, was zu tun ist, sondern stellt auch sehr gut den positiven Effekt dar, den das Ausschalten des count-Modes verursacht.

Zu Sebastians Blog-Artikel gelangt ihr hier

Kniff 2 – Expands nur an Stellen, wo sie gebraucht werden

Mit Hilfe von expands können Entitäten mit Zusatzinformationen gefüllt werden. Im Backend werden dazu nach dem Auslesen der eigentlichen Entität die Zusatzeigenschaften aus weiteren Tabellen geladen. Das kann mitunter ganz schnell gehen, so zum Beispiel bei einfachen 1:1 Beziehungen, kann aber auch länger dauern. Für letzteres prädestiniert sind sowohl 1:n Beziehungen als auch Multi-Expands (Beispiel SD-Auftrag -> ToDebitor -> To Address)
Hier gilt es eine relativ einfache Regel zu beachten: Ich nutze expands nur an Stellen, wo ich sie brauche! Der Klassiker ist die Auswahl der expands bei Übersichtsseiten gegenüber der Auswahl auf Detailseiten.
Auf einer Übersichtsseite (mit einem Table- oder List-Control) brauche ich nur Informationen, die in der Tabelle angezeigt werden. Auf der Detailseite kann ich dann dagegen mehr Informationen anzeigen.
Ein einfaches Beispiel mit einer schlechten Umsetzung und einer guten:

Worklist-View
...
<Table items="{path: '/SalesOrderSet, parameters : { 
expand : ToBusinessPartner, ToBusinessPartner/ToContact, 
ToSalesOrderLineItem, ToSalesOrderLineItem/ToContact}}">
...
</Table>
...

In diesem Code-Snippet werden alle expands der SalesOrder sofort mitgeladen. Wir haben hier sowohl 1:n Beziehungen (SalesOrder -> ToSalesOrderLineItem) als auch Multi-Expands.
Besser wäre folgende Lösung:

Worklist-View
...
<Table items="{/SalesOrderSet}">
...
</Table>
...
Object-Controller
_onObjectMatched: function(oEvent) {
        var oId = oEvent.getParameter("arguments").id;			
	this.getView().bindElement({
		path: "/ProductSet('" + oId + "')",
		parameters: {
			expand: " ToBusinessPartner, ToBusinessPartner/ToContact, 
                        ToSalesOrderLineItem"
		},
	});
},

Die Expands werden in dieser Variante erst beim Springen auf die Detailseite geladen.

Kniff 3 – Reduzierung von Expands bei Aggregation-Bindings

Kniff 3 ist eine Erweiterung von Kniff 2, geht aber noch einen ganzen Schritt weiter. Selbst wenn ich expands bei einer Übersichtsseite benötige kann es Sinn machen unter Performance Gesichtspunkten auf sie zu verzichten.
Hier müsste dann entsprechend die fachliche Anforderung geändert werden.
Das gilt wie bei Kniff 2 auch schon vor allem für 1:n Beziehungen und Multi-Expands. An dieser Stelle werden die entsprechenden expands nämlich nicht nur für eine Entität sondern für n-Entitäten geladen, da wir es hier mit einem Aggregation Binding zu tun haben.
Ein einfaches Beispiel wäre wieder SD-Auftrag -> ToDebitor. Die fachliche Anforderung entspräche beispielsweise zu einem SD-Auftrag den Namen des Debitoren mit anzuzeigen. Bei 10.000 Debitoren im ERP und 20 angeforderten SD-Aufträgen würde bei einem einzigen Backend-Call 20 Mal innerhalb dieser 10.000 Debitoren nach dem richtigen Debitor gesucht werden. Dass das nicht performant sein kann, kann sich, glaube ich, jeder von euch recht einfach vorstellen.
Eine bessere Lösung wäre dagegen auf die Debitoren-Daten in der Übersichtsliste zu verzichten und diese erst auf der Detailseite anzuzeigen. Eine weitere Lösung, sofern die Anzeige in der Übersichtsliste gewünscht ist, kann z.B. sein einen Debitor per Popover on demand nachzuladen.
Beide Varianten haben gemeinsam, dass jeweils nur ein Debitor zur Zeit ausgelesen wird. Dadurch wird an dieser Stelle das Laden etwas länger dauern, was aber ein Klacks ist hinsichtlich der Zeitersparnis beim Laden der Übersichtsliste.

Kniff 4 – Keine unnötigen Calls ans Backend

Kniff 4 ist ein Phänomen, dass an unterschiedlichsten Stellen in der Anwendung auftreten kann. Als allgemeine Richtlinie lässt sich sagen, dass man als Entwickler ein Auge auf den Network Trace haben sollte, um unnötige Backend-Calls zu vermeiden.
Ein relativ anschauliches Beispiel kann die Verwendung des liveChange- anstatt des change-Event eines Input-Controls sein, bei dem ein nachgelagerter, asynchroner Call geschieht (bspw. bei einem Suchfeld). Das liveChange-Event (und somit der Backend-Call) wird nach der Eingabe eines jeden Buchstaben gefeuert, wohingegen das change-Event nur nach Bestätigung der kompletten Eingabe erfolgt (Beim Verlassen des Controls, per Suchbutton oder Enter, je nach Implementierung)

Object-View
<input livechange="onSearch">
vs.
<input change="onSearch">
Object-Controller
onSearch: function(oEvent) {
	var oBinding = this.getView().byId(„table“).getBinding(„items“);
	oBinding.filter(new sap.ui.model.Filter(
            {path: "Name", operator: "EQ", value1: oEvent.getSource().getValue()}

Dieses Beispiel zeigt das klassische Filtern einer Tabelle. Die Methode onSearch macht durch den Filter-Aufruf einen Call ans Backend. Obwohl die beiden Varianten relativ gleich aussehen unterscheiden sie sich massiv im Verhalten und somit auch in der Performance.
Ein anderes Beispiel kann die initiale Filterung einer Liste nach dem Laden der Anwendung sein. Hier wird durch das XML-Binding bereits ein Call ans Backend gemacht. Da diese Liste aber eigentlich vorgefiltert sein soll muss ich direkt danach eine Filterung auf diese Liste machen. Zack, da war er wieder, der zweite Backend Call.
Um diesen Backend-Call zu umgehen könnte man die Filterung direkt im XML mit machen (siehe dazu einen anderen Blog-Beitrag von mir) oder aber das Binding nachgelagert im Controller statt im View durchführen.

Kniff 5 – Nutzung von Paging bei Aggregation Bindings

Schon wieder Aggregation Bindings. Warum eigentlich? Kurze Antwort: Weil es an dieser Stelle einfach so wahnsinnig viel ausmacht, wenn Performance eingespart werden kann. Diese Einsparung wird nämlich sofort multipliziert, weshalb sich n-fach darüber freuen lässt.
Kniff 5 besagt so viel wie: Wenn ein ODataModel auf eine Aggregation trifft und gebunden werden soll, dann sollte immer die Paging Funktionalität des Controls genutzt werden.
Einfache Beispiele sind das Table- und das List-Control, welche beide ein Paging anbieten. (Als kleine Hintergrundinformation: Paging setzt die Parameter $top und $skip bei den Backend-Calls. Diese müssen im Backend richtig verarbeitet werden, damit ein Paging den gewünschten Effekt hat)
Sofern ein ODataModel auf die aggregation eines Controls gebunden werden soll, dass kein Paging anbietet, so sollte man sich das mindestens drei Mal überlegen. Gegebenenfalls gibt es ja ein anderes Control, mit dem sich die eigenen Anforderungen umsetzen lassen? In der Vergangenheit habe ich bei meinen Implementierungen für das Filtern von Listen vom ViewSettingsDialog Abstand genommen und nur noch den FacetFilter-Dialog verwendet. Der einfache Grund dafür war, dass der ViewSettingsDialog kein Paging anbietet, der FacetFilter dagegen schon.

...
<Table ... growing="true" growingthreshold="15"></Table>
...

Im Table-Control entscheiden die beiden Eigenschaften growing (Paging ja/nein) und growingThreshold (Anzahl der max. Einträge pro Call/Page) über das Paging.

Fazit

Das soll es für den ersten Teil gewesen sein. Kommentiert und diskutiert gerne hier die gezeigten Möglichkeiten. Sind eure Fiori Anwendungen auch zu langsam? Habt ihr die hier vorgestellten Kniffe schon mal angewendet oder wurdet ihr durch diesen Beitrag ermuntert das zu tun?
Guckt auf jeden Fall wieder vorbei, spätestens beim zweiten Teil der Serie. Dort gibt es weitere fünf Kniffe um deine Fiori Anwendung zu beschleunigen.

 

Du interessierst dich für Fiori und hast Lust coole Projekte mit uns zu machen? Wir suchen dich! Schau doch mal in unserer Stellenbeschreibung vorbei.

Über den Autor

Sebastian Kielhorn

Kommentar verfassen