Performance Optimierung Teil II und noch mehr Pizzen

Mittwoch, 01.05.19. Bielefeld.

Heute, am Tag der Arbeit, arbeite ich nicht, daher etwas über gestern.

Mein Kunde lacht entspannt. Ein amüsiertes und wissendes Lachen, in dem ein »der Hakan wieder« sich versteckt. Der Kunde kennt mich. Ich sitze endlich an der finalen Präsentation meiner Pizza-Story (Performance Optimierung). Die nahm unerwartet mehr Zeit in Anspruch als gedacht. Manchmal gleicht das Verbessern eines Programms einem Stich in ein Wespennest.

Ich machte mir sogar über das Wochenende Gedanken darüber. Das ist bei mir so. Ich kann nicht aufhören und sitze solange an einem Problem, bis ich es löse, weil ich weiß, dass ich es lösen kann. Ich machte dem Kunden sogar den Vorschlag, dass ich die Stunden für den unerwarteten Mehraufwand nicht in Rechnung stelle, wenn meine erneute Optimierung kaum Verbesserungen zeigt.

Da lachte mein Kunde wieder. Das klang verlockend.

Eigentlich sollte ich auch nur etwas Kleines umstellen. Keine große Sache. Aber wie das bei mir der Fall ist, schaue ich nicht nur ein wenig genauer, sondern auch mich um. Natürlich finde dann immer irgendetwas, das ich verbessern kann. Beim genauen Hinschauen fand ich an anderer Stelle gleich einen Fehler (damit hatte dieser Teil des Programms nie funktioniert und niemandem fiel es auf).

Vielleicht sollte ich mir wie ein Pferd Scheuklappen aufsetzen.

Denn etwas, das eigentlich nur einen halben Tag gedauert hätte, dauerte am Ende zwei Tage. Was gar nicht so wenig ist, denn ich bin als externer Berater nicht günstig – aber natürlich jeden Euro wert. Zum Glück ist mein Kunde auch davon überzeugt. Am Ende lieferte ich immer etwas Besseres als geplant oder gedacht. Bisher also alles noch gut.

Leider klappte das nicht gleich von Anfang an, was die Performance-Optimierung betraf. Ich hatte den Teil, wo ich die Anpassung vorgenommen hatte, gleich auch optimiert: Doppelt so schnell, halb so viele Daten und statt ca. 10% redundante (also überflüssige) Selektionen nur noch 0,02%. Das klang gut. Aber dann folgte der Test auf dem Testsystem.

Sehr ernüchternd. Ich erwartete eine Verbesserung zwischen 10% und 15%. Die erste Version jedoch schaffte knapp 5%. Das ist halt der Unterschied zwischen einem Entwicklungs-(Dev) und einem Qualitätssystem (Q): Die Datenqualität und vor allem die immense Datenmenge.

Was auf dem Dev-System mit annähernd nur 3.000 Datensätzen wunderbar schnell lief, summierte sich auf dem Qualitätssystem auf knapp 800.000 Datensätze, ganz zu schweigen, dass an anderer Stelle plötzlich Daten gefunden wurden. Eine Stelle jenseits meiner Anpassungen und somit außerhalb meines Blickwinkels (also trage ich doch irgendwelche Scheuklappen).

Diese Stelle hatte einen nicht zu unterschätzenden Effekt auf die Gesamtlaufzeit. Zwei ineinander geschachtelte Schleifen – der Tod jeder Geschwindigkeit. Davon eine mit knapp 800.000 Datensätzen und die andere etwas weniger mit knapp 600.000. Die äußere Schleife lief also dabei 800.000-mal über die Innere, die wiederum 600.000 seine eigenen Runden drehte. Damit multiplizierten sich die Schleifendurchläufe auf 480.000.000.000 Iterationen!

Je tiefer ich in das Programm eindrang, desto mehr verstand ich und desto absurder wurde es. Ich will hier nicht mit technischen Details langweilen (dazu später ein wenig mehr) und gleich zum Ergebnis kommen. Die Verbesserungen der verschachtelten Schleifen (Nested-Loops genannt) ging ganz schnell – und das ist etwas, was ich nicht verstehe, denn diese Optimierung ist kein Hexenwerk (siehe nächstes Kapitel). Danach ließ ich das alte Programm im Q-System gegen mein Verbessertes laufen. Das Ergebnis:

~8.000 Minuten zu ~800 Minuten.

Um das 10-fache schneller. Das kann sich sehen lassen.

Puh!

Jetzt habe ich wieder viel an Pizza gedacht, auch wenn das Wort diesmal kaum fiel. Daher gehe ich mir jetzt eine Pizza von Wagner kaufen. Flammkuchen nach Elsässer Art mit Käse und Lauch finde ich lecker! (Ist das schon Werbung oder muss Nestlé mir die Pizza erst kostenfrei ins Haus liefern?-)

Technisches zu meiner Optimierung

An dieser Stelle werde ich ein wenig technisch und skizziere meine Optimierung (es wird auch mal Zeit).

Datenbank: Optimierung des SQLs

Ich habe mit der SAP Transaktion ST05 die Datenbankzugriffe analysiert.

In meinem Fall zeigten sich sehr viele Einzelzugriffe und überflüssige Zugriffe auf die Datenbank. Ursache hierfür war das SELECT…ENDSELECT Statement im ABAP:

*bereite bestellte Pizzen
SELECT <pizza> 
       INTO @DATA(<eine_pizza>)
       FROM <lieferanten>
       WHERE pizza = @<bestellte_pizzen>.
       …
*      lese Zutaten zur Pizza
       SELECT <zutaten> INTO @DATA(<zutat>)
              FROM <kühlschrank>
              WHERE zutat_gehört_zu = @<eine_pizza>.
              packe_zutat_auf_pizza.
       ENDSELECT.
       …
ENDSELECT.

Dieses Statement holt sich jeden Datensatz einzeln aus der Datenbank und kann einfach mittels PACKAGE SIZE n verbessert werden:

*bereite bestellte Pizzen (verbessert)
SELECT <pizza> 
       INTO @DATA TABLE(<alle_bestellten_pizzen>)
       PACKAGE SIZE 10.000
       FROM <lieferanten>
       WHERE pizza = @<bestellte_pizzen>.

*      lese Zutaten zur Pizza
       SELECT <zutaten>
              INTO TABLE @DATA(<zutaten>)
              FROM <kühlschrank>
              FOR ALL ENTRIES IN @<alle_bestellten_pizzen>
              WHERE zutat_gehört_zu = @<alle_bestellten_pizzen>.
           
*      sortiere die Zutaten, um schnell darauf zuzugreifen
       SORT <zutaten> BY pizza.

*      ordne jetzt die Zutaten der jeweiligen Pizza zu
       LOOP AT <alle_bestellten_pizzen>) INTO DATA(<eine_pizza>).

*           finde die passende Zutat zur Pizza
            READ TABLE <zutaten> INTO DATA(zutat)
                                 WITH KEY pizza = <eine_pizza>
                                 BINARY SEARCH.
            IF sy-subrc = 0.
                packe_zutat_auf_pizza.
             ENDIF.
       ENDLOOP.

ENDSELECT.

ABAP: Optimierung der Nested-Loops

Für die Analyse des ABAP-Teil benutzte ich immer noch die SAP Transaktion ST12.

Bei der SQL-Optimierung oben habe ich bereits eine Lösung für die Verbesserung von verschachtelten Schleifen untergebracht. Hier nochmals gesondert aufgeführt. Zunächst ein Beispiel für Nested-Loops:

* ordne jetzt die Zutaten der jeweiligen Pizza zu
  LOOP AT <alle_bestellten_pizzen>) INTO DATA(<eine_pizza>).

*      finde die passende Zutat zur Pizza
       LOOP AT <zutaten> INTO DATA(zutat) WHERE pizza = <eine_pizza>.
            packe_zutat_auf_pizza.
       ENDLOOP.

  ENDLOOP.

Bei der verbesserten Version wird die lineare Suche durch eine binäre Suche ersetzt. Dazu muss die zweite Tabelle sortiert sein:

* sortiere die Zutaten, um schnell darauf zuzugreifen
  SORT <zutaten> BY pizza.

* ordne jetzt die Zutaten der jeweiligen Pizza zu
  LOOP AT <alle_bestellten_pizzen>) INTO DATA(<eine_pizza>).

*      finde die passende Zutat zur Pizza
       READ TABLE <zutaten> INTO DATA(zutat)
                            WITH KEY pizza = <eine_pizza>
                            BINARY SEARCH.
       IF sy-subrc = 0.
          packe_zutat_auf_pizza.
       ENDIF.
       
  ENDLOOP.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.