• Bitte schaltet eure Ad Blocker aus. SLinfo kann nur betrieben werden, wenn es durch Werbung Einnahmen erzielt. Vielen Dank!!
  • Wir freuen uns, wenn du dich in unserem Forum anmeldest. Bitte beachte, dass die Freigabe per Hand durchgeführt wird (Schutz vor Spammer). Damit kann die Freigabe bis zu 24 Stunden dauern.
  • Wir verwenden Cookies, um Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf unsere Website zu analysieren. Sie geben Einwilligung zu unseren Cookies, wenn Sie unsere Webseite weiterhin nutzen.

Was ist besser: String oder Liste (Thema Speicherverwaltung)

argus Portal

Freund/in des Forums
Hallo

Wie im Titel angedeutet geht es um die zumindest bei Listen nicht optimale Speicherverwaltung seitens LSL.

Bevor ich jetzt eine Reihe von Versuchen starte, frage ich erstmal hier, ob jemand zuverlässig weiss, ob
das Elemente ersetzen, löschen, hinzufügen usw. speicherschonender (Fragmentierung) bei einer der beiden Varianten ist, oder ob beides aufs selbe hinausläuft.

Beispiel, in welchem ich das mittlere Element ersetze:

string s;
list l;

s = "abc";
l = ["a","b","c"];

// String
s = llDeleteSubString(s,1,1);
s = llInsertString(s,1,"d");

// Liste
l = llListReplaceList(l,["d"],1,1);



P.S. Ich rede natürlich von Situationen, in denen Vorgänge wie oben sehr oft ablaufen.

Aktuell arbeite ich an einem Spiel in dem ich Undo-Schritte in einem Ringpuffer unterbringen will. Es soll beliebig vor und zurück navigiert werden können.
 
Zuletzt bearbeitet:
Ich habe, nachdem keine Antwort kam, einen Test anhand obiger Beispiele ausgeführt. Ergebnis: Bei Verwendung der
Listenmethode wird nahezu das doppelte an Speicher verbraucht. Die string-Methode arbeitet zwar deutlich langsamer (immerhin
zwei Funktionsaufrufe statt einem) ist aber offenbar in Situationen vorzuziehen, in denen viele Berechnungen nötig sind (wie eben
z.b. bei einem Undo-Buffer).
 
Zuletzt bearbeitet:
String-Funktionen legen mind. eine Kopie des ganzen Strings an.
s belegt möglicherweise 12byte für den String + 1 byte pro Char. Also 15 byte. Macht die Funktion eine Kopie des Strings, werden noch mal mind. 15 byte Speicher belegt. Eventuell auch 30 byte. So dass erst mal bis zu 45 byte belegt sind.

Wenn du Listen-Funktionen wie llListReplaceList in LSL verwendest, dann wird dabei ebenfalls intern mind. eine Kopie der ganzen Liste angelegt.
l belegt so 15 byte für die Liste und für jeden String darin noch mal 4byte + 1 byte pro Char, also 30 byte insgesamt. Werden Kopien angelegt, dann werden zusätzlich noch mal 30, eventuell auch 60 byte belegt. So dass erst mal bis zu 90 byte belegt sind.

Der zusätzliche Speicher muss natürlich erst mal vorhanden sein, auch wenn der Mono-Garbage-Collector den wieder aufräumt.

In wie fern nun was genau schneller ist kann ich so nicht sagen - auch Listen-Funktionen brauchen manchmal bisschen Rechenzeit, vor allem wenn es sich um wirklich große Listen handelt. Das müsste man wohl einfach im Einzelfall testen.
 
Hast du auch schon llList2List getestet, statt llListReplaceList?

Deine Frage lässt sich nicht pauschal beantworten, da das Ergebnis stark von vielen verschiedenen Faktoren abhängig ist, wie die Länge der einzelnen Einträge, die Gesamtlänge usw.

Kannst du stattdessen auch 20 (wie lang auch immer deine Undo-Liste sein soll) einzelne Variablen verwenden?

Eine Schleife mit vielen if-Conditionals ist oft viel schneller abgearbeitet als kompliziertere Listen- oder Stringfunktionen.

Womöglich können dir auch die JSON-Funktionen hier helfen.
 
Ich möchte eine allgemeine Lösung finden. Die Undo-Funktion brachte mich nur wieder zu dem Thema, das mir schon länger ein Dorn im Auge ist.

Variablengestützt mit einem If-Konstrukt wäre ansonsten natürlich eine Möglichkeit.

llList2List hilft mir hier nicht weiter, da es besonders auch um das laufende Ändern von Einträgen geht.


JSON werde ich mir einmal näher ansehen. Damit habe ich mich noch nicht befasst.

Erstmal entwickle ich eine universell einsetzbare Methode (eine Sammlung von Funktionen) mit der man Arrays verwalten kann. Also so etwas wie add, del, set (entspr. sozusagen "replace ") und get. Alles indexbasiert. Und es kommt ohne Listen aus; stattdessen arbeite ich nur mit Strings. Die Elementgrösse ist variabel. Typen werden einfach gecastet.

Ich muss nur noch ein paar Härtetests in Sachen Fragmentierung durchführen.

Bei der Gelegenheit: Ich hatte vor einiger Zeit einmal hier im Forum vermutet, das der Aufruf von llGetFreeMemory eine Garbagecollection auslöst. Das scheint leider nicht der Fall zu sein. Und wenn, dann ist der Effekt vernachlässigbar.
 
Zuletzt bearbeitet:
Ich möchte eine allgemeine Lösung finden.
Allgemein lässt es sich leider nicht umsetzen.
Strings sind vom Speicher her besser, solange sie nicht im globalen Scope deklariert sind.
Es hängt wirklich sehr stark von einzelnen Faktoren ab: ob man lange oder kurze Einträge hat, wieviele man hat, wie groß die Gesamtlänge ist, in welchem Scope man es braucht, und in was es konvertiert werden muss, und wie performant das sein soll.

llList2List hilft mir hier nicht weiter
Mit llList2List meinte ich, dass du noch probieren kannst, ob das weniger Memory verbraucht als llListReplaceList in deinem Fall (was ich vermute).

Strings benötigen global mehr Ressourcen als lokal.

Also so etwas wie add, del, set (entspr. sozusagen "replace ") und get.
Und eine Funktionssammlung an sich wird schon viel an Ressourcen verbrauchen, da sich User-generierte Funktionen bereits selbst auf den Speicherbedarf auswirken.

Ein einfaches ReplaceAll ist schon ein riesengroßer Brocken für eine einzelne Funktion, in LSL. Du solltest immer versuchen, so viel wie möglich auf die von LSL bereitgestellten Funktionen zurückzugreifen, das ist in den allermeisten Fällen performanter und ressourcenschonender.


Wenn du gerade die Muße dazu hast, spricht ja nichts dagegen einfach mehrere Methoden parallel auszuprobieren.


Hier noch ein paar Artikel und Tipps über ressourcen-sparende Programmierung mit LSL:
Integer und Vektoren verwendet man besser global,
eine Methode Speicher zu sparen ist es, user-generierte Funktionen zu entfernen und den Code stattdessen in das Linkmessage-Event zu stecken, und bloß llMessageLinked(LINK_THIS,.. mit entsprechenden Parametern aufrufen,
das gleiche gilt für andere Events, die man in ein Helfer-Skript auslagern kann, welches die Events weiterleitet an das Linkmessage-Events des 'Haupt'-Skripts,
Konstanten ersetzt man bei speicher-kritischen Skripts besser durch Literale in LSL,
für den GarbageCollector hilft es möglicherweise (das ist Spekulation) vor und nach großen Listenoperationen ein kleines Delay einzubauen.

http://wiki.secondlife.com/wiki/Category:Memory_Conservation
http://wiki.secondlife.com/wiki/LSL_Script_Memory
(Hier sind auch noch zusätzlich zwei nützliche Links ganz unten auf der Seite zu Omei's Seiten): http://wiki.secondlife.com/wiki/User:Pedro_Oval/Mono_code_memory_usage
 
(...)

Ich muss nur noch ein paar Härtetests in Sachen Fragmentierung durchführen.

(...)

Kleine Ergänzung noch:
Speicherfragmentierung stellt bei Mono kein Problem mehr dar. Das ist nur ein Problem der LSO-Scripts.

LSO-Scripts haben immer einen fixen, kompakten Speicherblock mit 16kB und da muss auch alles rein passen. Istnicht genug Platz um alles in den Speicher zu packen, dann gibt es eine Stack-Heap Collision. Und bei einem TP oder wenn man ein Script "eingepackt" hat, wurden die 16kB am Stück komprimiert und z.B. auf einen anderen Sim oder "ins Inventory" (auf dem Asset Server) kopiert.

Mono in SL funktioniert da anderes, mit "Mikroprogrammen":
LSL Scripte in Mono haben keine fixen Speicherblöcke mehr, in denen sie komplett liegen müssen. D.h. Mono arbeitet nicht mit einzelnen, kompakten 16kB Blöcken, sondern mit vielen verteilten kleinen Blöcken (< 1k), die irgendwo im Speicher liegen können, wo gerade Platz ist. Eben weil der Speicher in Mono dynamisch belegt wird. Deswegen ist die Größe von Mono-Scripts auch variabel und kann irgendwas von 4kB oder so (minimale Größe eines Mono-Scripts) bis theoretisch mehrere 100kB sein. Eine Stack-Heap-Collision gibt es nicht mehr. Allerdings geht das nur sehr sehr kurze Zeit mit >65kB, denn schließlich läuft da nicht nur der Garbage Collector - es wird auch ständig immer wieder geprüft ob das Script mehr als 65kB hat. Und wenn das Script > 65kB hat, dann wird es von der LSL-Enginge einfach "abgewürgt" und nicht mehr weiter ausgeführt. Dazu taucht dann auch im Viewer die alte LSO-Fehlermeldung "Stack-Heap-Collision" auf, die eben bei einem Crash eines Scriptes wegen zu wenig Speicher vom Viewer ausgegeben werden.

Werden Mono-Scripts auf einen anderen Sim transportiert (oder eingepackt), dann werden eben all diese irgendwo im Speicher verteilten Script-Fragmente eingesammelt und in ein Archiv verpackt, dass dann an den anderen Sever verschickt wird um dort bei bedarf wieder irgendwo in den Speicher gepackt zu werden, wo eben Platz ist. Zudem gibts noch den Garbage Collector, der den Speicher auch immer wieder defragmentiert.

Die Fragmentierung des CLR-Bytecodes, in den die LSL Scripte letztendlich übersetzt werden, die ist dabei aber nicht nur einkalkuliert - sondern direkt ein Feature von Mono. Dadurch können Mono-Scripts eben z.B. Bytecode-Sharing. D.h. da kann ein Mono-Script direkt auf den Bytecode einer Scriptkopie zugreifen, so dass zwar das erste Script möglicherweise 40kB belegt, das zweite, durch eine direkte Kopie des ersten entstandene, braucht eventuell aber nur 6kB - weil es eben nur Variablen neu belegen muss, aber ansonsten auf Funktionen und Teile des originalen Scripts zugreifen kann.
 
Zuletzt bearbeitet:
Strings sind vom Speicher her besser, solange sie nicht im globalen Scope deklariert sind.

Bei meiner "Funktionensammlung" sind sie allesamt lokal in den Funktionen angelegt.


Mit llList2List meinte ich, dass du noch probieren kannst, ob das weniger Memory verbraucht als llListReplaceList in deinem Fall (was ich vermute).

hm, sprechen wir gerade aneinander vorbei ? Ansonsten wüsste ich nicht, wie ich mit llList2List ein Listenelemnt durch ein anderes ersetzen könnte. Und genau darum geht es mir ja.

Und eine Funktionssammlung an sich wird schon viel an Ressourcen verbrauchen, da sich User-generierte Funktionen bereits selbst auf den Speicherbedarf auswirken.

Ja. Wird es knapp, oder es ist zu erwarten, dass das Projekt grösser wird, passe ich es direkt zu Beginn an.
Aber bei kleineren Scripten werde ich meine Funktionen einsetzen, wie sie sind und sozusagen den "Komfort" und die bessere Übersichtlichkeit nutzen. Mit einem liste = set(liste,4711,"eintrag") arbeitet es sich leicher als mit dem, was
LSL anbietet ;-)

Wenn du gerade die Muße dazu hast, spricht ja nichts dagegen einfach mehrere Methoden parallel auszuprobieren.

Ich beschäftige mich die nächste Zeit immer wieder damit. Ziel ist, eine Art Werkzeugkasten an Funktionen und "Arbeitsanweisungen" für verschiedene Einsatzzwecke zu entwerfen.

Hier noch ein paar Artikel und Tipps über ressourcen-sparende Programmierung mit LSL:

Danke ! Ich arbeite mich da mal durch :)
 
Kleine Ergänzung noch:
Speicherfragmentierung stellt bei Mono kein Problem mehr dar. Das ist nur ein Problem der LSO-Scripts.

Ich mache die Erfahrung, das sehr häufiges Aufrufen von Listenfunktionen irgendwann zum Stackheap-Fehler führt, wenn das Script relativ umfangreich ist. Und zwar ohne das ich dabei immer mehr Speicher benötige. Das sieht für mich nach Fragmentierung aus. Daher hatte ich diesen Verdacht.

Bei wirklich grossen Scripten gehe ich dem Problem bislang aus dem Weg, indem ich "Listen" in eigene Scripte auslagere und diese mittels link-messages steuere. Kommt zum Glück nur selten vor, dass das nötig ist.
 
Ich mache die Erfahrung, das sehr häufiges Aufrufen von Listenfunktionen irgendwann zum Stackheap-Fehler führt, wenn das Script relativ umfangreich ist. Und zwar ohne das ich dabei immer mehr Speicher benötige. Das sieht für mich nach Fragmentierung aus. Daher hatte ich diesen Verdacht.

(...)


Da wird dann intern eben wohl trotzdem > 65kB Speicher von den Listen-Funktionen belegt, auch wenn es sich aus dem LSL-Code dann so nicht wirklich erschließt. Also wird das Script von der VM abgewürgt. Denn mit entsprechender Programmierung kann man kurzfristig schon mehr Speicher belegen als die 65kB.

Leider gibts bei SL keine Möglichkeit irgendwie an den Bytecode oder die reale Speicherbelegung der VM zu kommen, aber es kann gut sein, dass einige der Listen-Funktionen bei jedem Aufruf intern z.B. bei schnellen Loops erst mal ein paar neue Variablen und damit Speicher belegt. Und dass der Garbage Collector dann nicht schnell genug den Speicher wieder frei macht. Gerade bei großen Listen mit vielen Einträgen, die dann wohl als großes Array intern angelegt werden, kann so einiges an Speicher zusammen kommen. Prüft der GC dann auch die max. zulässige Größe kommt eben die Notbremse.

Helfen kann es da dann z.B. möglicherweise wenn man z.B. eben nicht zu viel in einen einzelnen großen while- oder if- Loop packt, sondern das ganze mit mehreren getrennten Loops verarbeitet, so dass der Garbage Collector zwischen dem Abarbeiten der Loops Zeit kriegt, in der er aufräumen und den Speicher frei machen kann. Denn das geht nur, wenn die VM kurz mal komplett angehalten werden kann, wenn also alle Scripts gerade an einer internen "STOP?" Marke angekommen sind, die vom Bytecode-Compiler an vielen Stellen in die Scripts eingebaut werden.

statt einem

Code:
integer i; 
for (i=0;i<100;i++){
    //Listenfunktion
}

oder einem

Code:
integer i; integer j;
for (i=0;i<10;i++){
    for(j=0;j<10;j++){
        //Listenfunktion
    }
}

kann man auch ein

Code:
integer i; 
for (i=0;i<50;i++){
    //Listenfunktion
}
for (i=50;i<100;i++){
    //Listenfunktion
}

machen. Unter dem Strich kommt da erst mal das selbe raus: 100 mal wird die Listenfunktion aufgerufen. Der Unterschied ist, dass bei der letzten Version eben nach 50 Iterationen vom Compiler problemlos ein "Stop?" eingebaut werden kann, so dass der GC eben Zeit hat um bisschen aufzuräumen. Ob das was bringt muss man aber im Einzelfall wohl testen. Ich weiß leider nicht wann genau bzw. nach welchen Kriterien diese Stop-Marks in den Bytecode eingebaut werden.
 
Helfen kann es da dann z.B. möglicherweise wenn man z.B. eben nicht zu viel in einen einzelnen großen while- oder if- Loop packt, sondern das ganze mit mehreren getrennten Loops verarbeitet, so dass der Garbage Collector zwischen dem Abarbeiten der Loops Zeit kriegt, in der er aufräumen und den Speicher frei machen kann. Denn das geht nur, wenn die VM kurz mal komplett angehalten werden kann, wenn also alle Scripts gerade an einer internen "STOP?" Marke angekommen sind, die vom Bytecode-Compiler an vielen Stellen in die Scripts eingebaut werden.


Das klingt interessant. Ob man die Mechanismen wirklich so "ausnutzen" kann, muss natürlich erstmal geprüft werden. Ich werde dazu ein altes Projekt ausgraben, bei dem sich dieser Test anbietet. Dazu brauche ich allerdings Zeit und Ruhe. Sollte ich ein sicheres Resultat gewinnen, werde ich es hier nennen.
 

Users who are viewing this thread

Zurück
Oben Unten