Andrea: Lokale Variable

Hallo Forumer,

Ich habe mich vor kurzem mit einem JavaScript-Newsticker beschaeftigt und mir sind da ein paar Gedanken zu JS gekommen, zu denen Euch vielleicht etwas einfaellt.

Rein theoretisch koennte ich doch bei JavaScript mit einer Funktion, die sich rekursiv immer wieder selber aufruft und lokale Variable verwendet, einen Stack-Overflow produzieren (wenn das Script lange genug laeuft). Muss ich mir sogar bei JS Gedanken um den Stack machen?

Oder macht das der JS-Interpreter wie der Perl-Interpreter und gibt nicht mehr benutzte Variable einfach frei?

Gruss,
Angel

  1. Hallo Angel

    Rein theoretisch koennte ich doch bei JavaScript mit einer Funktion, die sich rekursiv immer wieder selber aufruft und lokale Variable verwendet, einen Stack-Overflow produzieren (wenn das Script lange genug laeuft). Muss ich mir sogar bei JS Gedanken um den Stack machen?

    Bei Rekursion auf jeden Fall! Da ist Stack-Overflow in JavaScript genauso moeglich wie in anderen Sprachen. Und irgendwann passiert das auch. Nur weiss keiner wann genau. Das ist von Browser zu Browser verschieden und wahrscheinlich auch von der User-Umgebung abhaengig (verfuegbarer Arbeitsspeicher usw.). Rekursive Funktionsaufrufe sollte man daher unbedingt in ein window.setTimeOut("Funktion()",100) verpacken (die 100 sind hier nur ein Beispiel - das sind immer noch 10 Aufrufe pro Sekunde, aber schon deutlich weniger als "so viel wie moeglich"). Oder du definierst einen Maxwert und zaehlst bei jedem Funktionsaufruf einen Zaehler hoch und rufst die Funktion nur solange auf, wie der Zahler kleiner als der Maxwert ist. Wie hoch der Maxwert sein sollte? Keine Ahnung. Aber ein paar Tausend sollten auf jeden Fall drin sein.

    viele Gruesse
      Stefan Muenz

    1. Hallo Stefan,

      Bei Rekursion auf jeden Fall! Da ist Stack-Overflow in JavaScript genauso moeglich wie in anderen Sprachen. Und irgendwann passiert das auch. Nur weiss keiner wann genau. Das ist von Browser zu Browser verschieden und wahrscheinlich auch von der User-Umgebung abhaengig (verfuegbarer Arbeitsspeicher usw.). Rekursive Funktionsaufrufe sollte man daher unbedingt in ein window.setTimeOut("Funktion()",100) verpacken (die 100 sind hier nur ein Beispiel - das sind immer noch 10 Aufrufe pro Sekunde, aber schon deutlich weniger als "so viel wie moeglich"). Oder du definierst einen Maxwert und zaehlst bei jedem Funktionsaufruf einen Zaehler hoch und rufst die Funktion nur solange auf, wie der Zahler kleiner als der Maxwert ist. Wie hoch der Maxwert sein sollte? Keine Ahnung. Aber ein paar Tausend sollten auf jeden Fall drin sein.

      Meine Loesung war, vorsichtshalber in der rekursiven Funktion vordefinierte globale Variablen zu verwenden. Nach meinem Verstaendnis reduziert sich damit die 'Last' auf dem Stack auf die Return-Adresse pro Aufruf. Trotzdem wird der Stack wohl irgendwann ueberlaufen. Realistisch gesehen bleibt aber wohl niemand so lange auf einer WebSite ...

      Damit bleibt aber meine Frage bestehen, was der JS-Interpreter mit den lokalen Variablen macht. Freigeben oder festhalten?

      Gruss,
      Angel

      1. Hallo,

        Damit bleibt aber meine Frage bestehen, was der JS-Interpreter mit den lokalen Variablen macht. Freigeben oder festhalten?

        in rekursiven Funktionen muß ein Interpreter grundsätzlich alle lokalen Variablen aufbewahren, bis die momentane Rekursionstiefe wieder verlassen wird - sonst gibt es Inkonsistenzen. Ein anschauliches Beispiel ist z.B. die Routine aus dem Spiel "eggsweeper" (hier mittlerweile wohl bekannt *g*, ansonsten auf http://www.atomic-eggs.com...), in der die umliegenden Eier automatisch "aufgedeckt" werden, wenn man eine "0" erwischt hat (analog zum bekannten Windows-Minesweeper).

        In dieser Routine wird zunächst geguckt, ob das Ei an der entsprechenden Position "nuklear verseucht" (oder was auch immer) ist. Wenn nicht, werden die verseuchten Eier in der unmittelbaren Umgebung (die 8 Nachbarfelder oben-links, oben, oben-rechts, links, ...) gezählt. Die Zahl der verseuchten Nachbarn wird an der Stelle des ausgewählten Ei's dargestellt, und das Feld wird als "aufgeklappt" markiert.

        Wenn nun die Anzahl der verseuchten Nachbarn 0 beträgt, ist klar, daß man die umliegenden 8 Eier sowieso gefahrlos aufdecken kann - daher kann das JavaScript das ja auch gleich automatisch mit übernehmen. Es ruft dazu einfach rekursiv die selbe Routine der Reihe nach mit allen 8 Nachbarfeldern auf (und guckt natürlich vorher nach, ob die nicht schon vorher aufgeklappt waren - sonst gibt's u.U. 'ne Endlosrekursion).
        Dabei ist natürlich wichtig, daß die Position, von wo aus die umliegenden Felder aufgedeckt wurden, gespeichert bleibt, sonst findet die Routine nicht mehr "zurück". Die Positionen werden also jeweils im Funktionsaufruf als Parameter übergeben und erhalten in jeder Rekursion eigenen Speicherplatz auf dem Stack als lokale Variable.

        Meiner Erfahrung nach läuft bei den gängigen Browsern wie Netscape 4 und IE 4/5 der Stack leider sehr schnell über. So ab Rekursionstiefen der Größenordnung 100 steigen die Browser bereits aus (und Netscape 4.07 unter Linux z.B. eher noch früher als unter Win95...!) - was eigentlich auf heutigen Rechnern mit durchschnittlich >= 64 MB Speicher nicht unbedingt nachvollziehbar ist.

        Soviel erstmal zu "meinem Senf" in dieser Sache... <g>

        Viele Grüße

        Andreas

        1. Hi,

          in rekursiven Funktionen muß ein Interpreter grundsätzlich alle lokalen Variablen aufbewahren, bis die momentane Rekursionstiefe wieder verlassen wird - sonst gibt es Inkonsistenzen.

          Logo! Haette ich selber dran denken koennen ...

          Meiner Erfahrung nach läuft bei den gängigen Browsern wie Netscape 4 und IE 4/5 der Stack leider sehr schnell über. So ab Rekursionstiefen der Größenordnung 100 steigen die Browser bereits aus (und Netscape 4.07 unter Linux z.B. eher noch früher als unter Win95...!) - was eigentlich auf heutigen Rechnern mit durchschnittlich >= 64 MB Speicher nicht unbedingt nachvollziehbar ist.

          Ich bin ziemlich neugierig und so habe ich meine rekursive Funktion einfach mal 2 Stunden laufen lassen. Siehe da, der Speicherverbrauch von Netscape stieg von gut 8MB auf gut 52 MB (!!) an (pfui Andrea, schlecht geschrieben! ;-)). Daraus ergaebe sich zwanglaeufig demnaechst ein Problem.

          Nachdenklich stimmt allerdings, dass der Browser den Speicher auch dann nicht mehr freigegeben hat, als wieder von der Seite runter gegangen bin. Das ist unfein :-(

          Weitere Tests ergaben dann, dass der Speicher prizipiell nur ausserst zoegerlich frei gegeben zu werden scheint. In anderen Worten: Je laenger man surft, umso weniger freien Speicher hat man zur Verfuegung. Gut, das ist nicht weltbewegend neu, aber so genau hab' ich's bisher noch nie betrachtet.

          (Doch, ich hab' auch anderes zu tun ...)

          Gruss,
          Andrea

          1. Moin!

            Ich bin ziemlich neugierig und so habe ich meine rekursive Funktion einfach mal 2 Stunden laufen lassen. Siehe da, der Speicherverbrauch von Netscape stieg von gut 8MB auf gut 52 MB (!!) an (pfui Andrea, schlecht geschrieben! ;-)). Daraus ergaebe sich zwanglaeufig demnaechst ein Problem.

            Wow... 2 Stunden?! So weit kamen meine Endlosrekursionen nie - die hielten meist nur wenige Sekunden durch <g>. Was war das denn jetzt genau für eine Funktion? Eine, die sich mit setTimeout(...) selbst aufruft - oder war der rekursive Aufruf direkt ‚hardcoded‚ in der Funktion drin? Eigentlich dürfte imho bei setTimeout(...) ja gar kein Stack-Speicher verbraucht werden, weil die Funktion nach der gewissen Zeit vom Event-Handler anstatt von sich selbst neu aufgerufen wird - d.h. beim vorigen Aufruf wurde die Funktion normal beendet. Allerdings kann ich mich auch noch an so einen Tag erinnern, wo ich im Vordergrund eine Mail schrieb, während ich in einem verdeckten Fenster einen tickernden Newsticker vergaß... Plötzlich war die Swap-Datei 90MB groß, und ich dachte, mein Rechner sei von einem Virus befallen...

            Nachdenklich stimmt allerdings, dass der Browser den Speicher auch dann nicht mehr freigegeben hat, als wieder von der Seite runter gegangen bin. Das ist unfein :-(

            Tja... der gefürchtete "Memory-Leak" ist wohl immer noch einer der am schwersten zu findenden Bugs in großen Programmen (wie denn auch - funktioniert ja erstmal alles).

            (Doch, ich hab' auch anderes zu tun ...)

            Gut - als nächstes können wir uns dann ja mal den zunehmenden Fragmentierungsgrad einer Festplatte beim wiederholten Installieren und Deinstallieren von IE5 unter die Lupe nehmen ;-))

            Viele Grüße

            Andreas

            1. Hi Andreas,

              Moin!

              Moin? Nachmittags um 5? Mir scheint, Du hast einen reichlich verdrehten Tagesablauf ;-)

              Wow... 2 Stunden?! So weit kamen meine Endlosrekursionen nie - die hielten meist nur wenige Sekunden durch <g>. Was war das denn jetzt genau für eine Funktion? Eine, die sich mit setTimeout(...) selbst aufruft - oder war der rekursive Aufruf direkt ‚hardcoded‚ in der Funktion drin?

              Mein erster Versuch, einen Newsticker von http://dhtml.seite.net/ (den von der Startseite) anzupassen. Dort wurde die Ticker-Ausgabe mit setTimeout rekursiv aufgerufen. Allerdings nur so lange, bis der Text 'zuende' war. Das gefiel mir nicht, also habe ich daraus einen endlosen rekursiven Aufruf gemacht, wobei ich _alle_ vorher lokalen Variablen global deklariert habe. Etwa so:

              Globale_Variable;

              function ticker {
              Initialisierungen;
              Ticker_Ausgabe;

              setTimeout(ticker, 100);	  
              

              }

              Eigentlich dürfte imho bei setTimeout(...) ja gar kein Stack-Speicher verbraucht werden, weil die Funktion nach der gewissen Zeit vom Event-Handler anstatt von sich selbst neu aufgerufen wird - d.h. beim vorigen Aufruf wurde die Funktion normal beendet.

              Aha. Somit ist mein 'rekursiver' Aufruf gar nicht wirklich rekursiv?
              Dann haette ich mir die Sache mit den'globalen Variablen schenken koennen?
              Woher kommt dann der immense Speicherverbrauch?

              Gut - als nächstes können wir uns dann ja mal den zunehmenden Fragmentierungsgrad einer Festplatte beim wiederholten Installieren und Deinstallieren von IE5 unter die Lupe nehmen ;-))

              Nenene!
              Ich bin Programmierer, nicht SysOp oder sowas. Ich krieg' meine Systeme im Allgemeinen installiert (jedenfalls die mit WinX) und ich lang' da nur was an, wenn's unbedingt sein muss!

              Viele Gruesse,
              Andrea

              1. Hi Andrea!

                Moin? Nachmittags um 5? Mir scheint, Du hast einen reichlich verdrehten Tagesablauf ;-)

                Ja klar! Morgen ist dann wenn man aufsteht! Oder wann ist das bei Dir? ;-)

                Globale_Variable;

                function ticker {

                »»  Initialisierungen;
                »»  Ticker_Ausgabe;

                »»  setTimeout(ticker, 100);

                }

                Eigentlich dürfte imho bei setTimeout(...) ja gar kein Stack-Speicher verbraucht werden, weil die Funktion nach der gewissen Zeit vom Event-Handler anstatt von sich selbst neu aufgerufen wird - d.h. beim vorigen Aufruf wurde die Funktion normal beendet.

                Genau, es sei denn man setzt das Intervall kleiner als die Zeit, die zum Ausfuehren der Funktion benoetigt wird. Was dann passiert, ist wohl auch browserabhaengig. (Spekulationen darueber lass ich jetzt mal sein. Wenn man sowas macht, muss man sich sowieso erstmal um ein Parallelitaetsproblem kuemmern, ich meine, dass da nichts schief geht.)

                Aha. Somit ist mein 'rekursiver' Aufruf gar nicht wirklich rekursiv?
                Dann haette ich mir die Sache mit den'globalen Variablen schenken koennen?

                Gerade die lokalen Variablen sollen doch den Stack verbrauchen! Ansonsten muss man doch nur die "Ruecksprungadresse" speichern (um es mit Mitteln richtiger Programmiersprachen auszudruecken). Und da kannst Du auf einen Ueberlauf ziemlich lange warten, selbst wenn der (virtuelle) Stack begrenzt ist. (Virtuell weil dies ja nur vom Prinzip her soetwas wie einen Stack darstellt, aber nichts mit dem Stack zu tun hat, den z.B. das Browser-Programm selber verwendet. Uebrigens wird unter Windows NT der (echte) Stack vom Betriebssystem dynamisch vergroessert, wenn er nicht ausreichen sollte.)

                Woher kommt dann der immense Speicherverbrauch?

                setTimeout() und setInterval() haben beim Netscape for Win ein ziemlich grosses Memory-Leak. Man sollte die Funktionen daher nur einsetzen, wenn es unbedingt sein muss, und dann mit moglichst grossen Intervallen. Je kleiner das Intervall ist, um so eher faengt die Platte an zu rattern...  Als Geocities noch sein Logo in der rechten unteren Ecke hatte, hatten sie es mit einem Intervall von 0.1 sec immer wieder an die richtige Position gesetzt, da hat man von dem Memory-Leak praktisch nix gemerkt. Mich wundert, dass Du in der Funktion oben auch die 100ms verwendest. So hoch sollte der Speicherverbrauch dadurch nicht sein. Ausserdem duerfte da kaum der Eindruck einer Bewegung entstehen (10 Schritte pro Sekunde, mindestens 25 sollten es sein).

                Nun bleibt also die Frage - laeuft der (virtuelle) Stack irgendwann ueber? Also wenn ich einen JS-Interpreter schreiben muesste, wuerde ich einfach immer, wenn ich wieder Stack brauche, weiteren Speicher "ziehen". Fuer mein Interpreterprogramm ist das ja kein Stack, sondern normaler Arbeitsspeicher, den man einfach allokieren kann. Insofern sollte der virtuelle Stack des JavaScripts nur durch die Resourcen der ausfuehrenden Maschine begrenzt sein. Allerdings kann man darueber nachdenken, ob man denn nicht eine Obergrenze einbauen sollte, denn wenn ein JS 50 MB an Stack verbraet, dann wuerde ich schonmal davon ausgehen, dass es einfach ziellos im Kreis laeuft, sprich: abgestuerzt ist. (Manche Leute glauben, wenn ein Computer abgestuerzt ist, tut er nichts mehr, aber das Gegenteil ist der Fall. Er tut sogar besonders viel, laeuft meist irgendwie im Kreis, rechnet irgendwelche Dinge, ohne aus der Schleife herauszukommen. Deshalb kann das Programm dann keine Eingaben mehr entgegennehmen oder reagieren und es entsteht der Eindruck, es tut nichts mehr.) Bei Rekursionstiefe 100 wuerde ich diese Grenze allerdings noch lange nicht ziehen.

                Und "Muss ich mir sogar bei JS Gedanken um den Stack machen?" Also ich finde, um die Resourcen sollte man sich immer Gedanken machen. Aber der Speicherausbau moderner Computer sollte ein bisschen Rekursion eigentlich locker verkraften.

                Stefan hat geschrieben

                Rekursive Funktionsaufrufe sollte man daher unbedingt in ein window.setTimeOut("Funktion()",100) verpacken

                Naja, fuer mich soll eine rekursive Funktion auch irgendwann ein Ergebnis liefern (z.B. Primzahlenzerlegung oder Fakultaet berechnen (ich weiss, geht beides auch ohne Rekursion und effektiver)), nur das wird mit setTimout kaum moeglich sein. Wenn ich das jetzt richtig ueberblicke, macht Rekursion sowieso keinen Sinn, wenn man setTimout() verwenden kann.

                Calocybe - hat heute einfach keinen Bock zum Arbeiten *mmpf*

              2. Moin! ;-)

                Moin? Nachmittags um 5? Mir scheint, Du hast einen reichlich verdrehten Tagesablauf ;-)

                Hm... sobald man nördlich von Kassel oder so wohnt, ist "Moin" doch rund-um-die-Uhr gültig oder (bzw. es scheint sich allmählich auch nach Süden hin auszubreiten...)? Oder mal ganz unrekursiv und rund-um-die-Uhr ausgedrückt:
                function moin() { alert("Moin!"); window.setTimeout("moin()", 3600000); }
                moin();  
                //  :-))

                Viele Grüße

                Andreas