Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

5.2 Was passiert bei einem Funktionsaufruf

5.2 Was passiert bei einem Funktionsaufruf

Zunächst müssen Sie Sich bewusst sein, dass ein Programm auch nur Daten darstellen, die ebenfalls im Speicher liegen. Bei der Ausführung gibt es einen so genannten Programmpointer, welcher immer auf die Zeile verweist, welche ausgeführt werden soll. Dieser kann mit einem Sprungbefehl auf einen anderen Speicherbereich verweisen und genau dies wird bei einem Funktionsaufruf getan. Genau dies soll folgende Grafik veranschaulichen.

Programmablauf bei einem Funktionsaufruf

Wie Sie im rechten Teil sehen können, kann es vorkommen, dass es nicht zwangsläufig zu einem Sprung kommen muss. Der Compiler ist bei einfachen Funktionen in der Lage, den Funktionsaufruf durch den entsprechenden Code zu ersetzen. Dies hat zur Folge, dass die resultierende Anwendung etwas größer wird, allerdings entfallen alle Sachen, die ich gleich erklären werde. Da man aber nicht unbedingt Einfluss darauf hat, ob der Compiler inlint oder nicht (streng genommen schon, aber daran hängen mehrere wichtige Faktoren, auf die es mir jetzt nicht ankommt), gehen wir jetzt mal vom linken Fall aus.

Dabei passieren jetzt mehrere Sachen. Als aller erstes werden alle Registerinhalte gesichert und auf Standartwerte gesetzt. Dies kostet logischerweise Speicherplatz. Als nächstes wird eine Rücksprungadresse auf dem Stack gesichert, damit dem Computer nach Beendigung der Funktion bekannt ist, welche Zeile als nächstes ausgeführt werden soll. Hier wird wieder zusätzlicher Platz benötigt.

Als nächstes wird die komplette Funktion analysiert und alle Variablen auf dem Stack erzeugt, die evtl. benötigt werden. Dieser Aspekt ist ganz interessant, denn ggf. werden gar nicht alle benötigt. Hier zeigt sich, dass es im Grunde egal ist, an welche Stelle man in der Funktion Variablen deklariert. Der Compiler kann bis auf wenige Sonderfälle nicht entscheiden, ob eine Variable tatsächlich benötigt wird und somit erzeugt er alle.

Nach der Abarbeitung der Funktion, werden nun zuerst alle Variablen der Unterfunktion freigegeben. Anschließend springt der Computer zur gespeicherten Rücksprungadresse und weißt ggf. den Wert des Funktionsergebnisses, einer Variable zu. Abschließend werden das Funktionsergebnis und die Rücksprungadresse vom Stack entfernt und die gesicherten Register werden zurückgeholt.

Vereinfachter Aufbau des Stacks bei einem Funktionsaufruf

Der graue Bereich ist also der Bereich, der bei jedem Funktionsaufruf zusätzlich im Stack angelegt werden muss. Daraus ergeben sich jetzt offensichtliche Nachteile.

Mit jedem Funktionsaufruf müssen also viele Sachen im Hintergrund gemacht werden, welche Rechenzeit benötigen. Wenn man also rekursive Funktionen baut, also solche, die sich selbst aufrufen, kann es schnell vorkommen, dass der Stack voll läuft (meistens 1 MB). Der Sprung im Code führt dazu, dass der neue Code nachgeladen werden muss (also vom RAM in den Cache) und in ganz seltenen, aber extremen Fällen, muss der neue Codeabschnitt sogar von der Festplatte nachgeladen werden, was noch mehr Verzögerung bedeutet.

Sie sehen also, dass ein Funktionsaufruf teuer sein kann. Wir sprechen hier von Millisekunden, aber wenn man eine Schleife mit einer Million Durchläufen hat, in welcher eine Funktion aufgerufen wird, können sich diese Millisekunden zu Minuten aufsummieren.

Ich möchte Sie jetzt auf keinen Fall verschrecken! Sie sollten nur im Hinterkopf behalten, dass ein Funktionsaufruf den Quelltext zwar übersichtlicher macht, aber dass man sich diese Übersichtlichkeit teuer erkauft. Ein guter Programmierer setzt Funktionen mit Bedacht ein. Es macht keinen Sinn, eine kleine Berechnung in eine Funktion zu packen (vorausgesetzt der Compiler schafft nicht es, diese Berechnung zu inlinen, wovon man ausgehen muss). Hat man aber eine einzige Funktion, die über 100 Zeilen geht, ist es doch ratsam, jene aufzuteilen.

Spielentwickler handeln hier eher paranoid und versuchen so wenig wie möglich Funktionen zu benutzen, da jede Millisekunde darüber entscheidet, ob das Spiel flüssig laufen wird oder ruckelt. Sie sollten aber fürs erste lieber an die Übersichtlichkeit denken und eher mehr als zu wenige Funktionen benutzen. Erst in dem Moment, an denen das Programm tatsächlich schneller laufen muss (weil Wartezeiten unerträglich werden), sollten Sie anfangen zu Optimieren und Funktionen vereinigen.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012