Stackoverflow bei instanzierung eine großen Objekts.



  • Hallo Leute,

    ich habe eine Klasse, welche im Ctor eine große Menge

    Ungefährt so (fragt mich nicht wieso, ich muss das so tun (hab es übernommen))

    class MyBigObject
    {
       public MyBigObject()
       {
       this.Object_00001 = new ObjectX();
       //.... das 50000 mal
       }
    }
    

    wenn ich nun in einer Consolen App

    static void Main(string[] args)
          {
    var x = new ObjectX();
    }
    

    bekomm ich "Program aborted due Stack Overflow ").

    Nun Kann ich das Ding in nem Thread erzeugen mit angabe der maximalen Stackgröße :

    static void Main(string[] args)
            {
    MyBigObject x= null;
                var instThread = new Thread(() => 
                {
                    x = new MyBigObject();
                },1000000000);
    
    ......
    }
    

    das geht, aber ist das der richtige Weg?

    Und zum Verständnis:

    Wenn ich ein Breakpoint in "ObjectX" im Ctor (erste Zeile) setze, und das Programm ausführe, sehe ich dass der Speicher auf 1,5GB ansteigt das ganze dauert ca. 10 Sekunden. Dann singt der Speicher, wieder ab und er spring zum Breakpoint. Aber erst jetzt wird der Content instanziiert. und erst jetzt müsse der Speicher ansteigen.

    Aber wieso brauch ich da nen riesen Stack, wird das nicht alles auf dem Heap angelegt!?

    Vielen Dank



  • Um was für eine konkrete Klasse handelt es sich denn bei ObjectX, wenn diese alleine schon den StackOverflow erzeugt? Oder hast du dich bei deinen unteren Beispielen mit MyBigObject vertan?

    Und was genau meinst du mit //.... das 50000 mal (doch nicht etwas 50000 Zeilen???)?

    Vllt. ist auch einfach eine Eigenschaft falsch implementiert (welche im Konstruktor benutzt wird) und ruft sich rekursiv auf!

    PS: Du solltest Sätze auch zuende schreiben:

    ich habe eine Klasse, welche im Ctor eine große Menge

    ...???



  • @Th69 sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Um was für eine konkrete Klasse handelt es sich denn bei ObjectX, wenn diese alleine schon den StackOverflow erzeugt? Oder hast du dich bei deinen unteren Beispielen mit MyBigObject vertan?

    oh shit, danke ja.. habe es gerade oben geändert.

    @Th69 sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Und was genau meinst du mit //.... das 50000 mal (doch nicht etwas 50000 Zeilen???)?

    Doch sind 50000 Zeilen, die werden als source-code von nem builder generiert. (das is momentan leider so) . Aus einer Konfiguration wird die kompilierbare code gemacht.

    Habe mir schon überlege, dass ich die Konfig als Json file (string) übergebe und er baut das dann da drin.. aber egal.. ich muss das jetzt mal so hinnehmen.

    @Th69 sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Vllt. ist auch einfach eine Eigenschaft falsch implementiert (welche im Konstruktor benutzt wird) und ruft sich rekursiv auf!

    nein da wird nichts rekursive aufgerufen, das ist einfach ne rießen klass (15mB) groß, die wird kompiliert, und dann instanziert, und wie gesagt es geht ja auch, aber nur in dem Thread mit nem großen Stack.



  • @SoIntMan
    Ich versuche zu verstehen; wenn Du Dich vertan hast, ObjectX ist eigentlich MyBigObject. Was ist jetzt dann this.Object_00001 ?



  • @Helmut-Jakoby sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    @SoIntMan
    Ich versuche zu verstehen; wenn Du Dich vertan hast, ObjectX ist eigentlich MyBigObject. Was ist jetzt dann this.Object_00001 ?

    Sorry für das wir-war:

    class MyBigObject
    {
       public MyBigObject()
       {
       this.Object_00001 = new ObjectX();
      this.Object_00002 = new ObjectX();
    this.Object_00003 = new ObjectX();
       ///und weitere zeilen  this.Object_00003?> this.Object_50000
       }
    }
    


  • @SoIntMan
    Ich kann mit this. (ich mein den Punkt) nichts anfangen und schon garnichts mit der Zuweisung der neuen unbekannten Klasse ObjectX. Was macht z.B. ObjectX im ctor?

    Edit: Sorry😱 !!!! Hab gar nicht bemerkt, dass wir hier C# besprechen. Nichts für ungut.



  • Gibt es denn die Möglichkeit, den Codegenerator umzuschreiben?

    Das hier

    this.Object_00001 = new ObjectX();
    this.Object_00002 = new ObjectX();
    this.Object_00003 = new ObjectX();
    

    stinkt doch ganz grausam nach einem Array von ObjectX. Außderdem: was für Compilezeiten kommen denn raus, wenn man 50k Variablen hat?!



  • @wob sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Gibt es denn die Möglichkeit, den Codegenerator umzuschreiben?
    Das hier
    this.Object_00001 = new ObjectX();
    this.Object_00002 = new ObjectX();
    this.Object_00003 = new ObjectX();

    stinkt doch ganz grausam nach einem Array von ObjectX. Außderdem: was für Compilezeiten kommen denn raus, wenn man 50k Variablen hat?!

    ja es ist grausam, ich weiß... aber das Problem ist . den Code Generator kann man umbschreiben, ABER:

    das ist nur ein pseude code, in wirklichkeit ist Das ObjectX irgend ein generiertes Object ,welches auch Properties hat.

    hier etwas komplexter pseudo:

    class MyBigObject
    {
       public MyBigObject()
       {
       this.Root = new ObjectX(); //ObjectX hat ein feld A ,usw.
       this.Root.A = new ObjectY();
       this.Root.B = new ObjectZ();
      this.Root.B.A = new ObjectA();
       // und das ganze in irgendeiner form 50000 mal.
       }
    }
    

    Ich könnte jetzt dem MyBixObject ein string json geben, und anhand der json file, das ganze model auch erstellen lassen, ohen den statischen code. das wäre die "not" lösung.

    Aber ich versuche noch zu verstehen wie ich nen optimierten generieten code machen könnte..



  • @SoIntMan sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    class MyBigObject
    {
       public MyBigObject()
       {
       this.Root = new ObjectX(); //ObjectX hat ein feld A ,usw.
       this.Root.A = new ObjectY();
       this.Root.B = new ObjectZ();
      this.Root.B.A = new ObjectA();
       // und das ganze in irgendeiner form 50000 mal.
       }
    }
    

    Dein Problem ist doch, dass du zu viele Variablen in MyBigObject hast, d.h. root1, root2, root3 etc, oder?

    Dann wird da eben folgendes draus:

    this.root[0] = new RootObject();
    this.root[0].A = ...
    this.root[0].B = ...
    ...
    this.root[50k] = new RootObject();
    this.root[50k].A = ...
    this.root[50k].B = ...
    

    Ich kann sonst auch nichts dazu sagen. Ein Umbau so erscheint mir aber einfacher als irgendwie auf einmal einen String zu parsen, wobei vielleicht letztes für dich auch sinnvoll ist, wenn die Klassen ObjectX/Y/Z eigentlich nur irgendwelche Daten halten und keine Funktionalität haben.



  • @SoIntMan: Was mir noch eingefallen ist: schau dir mal den IL-Code des Konstruktors an (z.B. mit ILSpy) und überprüfe mal, ob dort ein oder mehrere .maxstack-Anweisungen drin sind (diese sollten eigentlich nur bei lokalen Variablen und nicht bei direkten Zugriffen auf die Klassenmember benötigt werden).
    Aber probiere auch mal die Release-Version aus.



  • Vielleicht ist auch das Problem dass der JIT nen Stack-Overflow verursacht beim Versuch die lange Funktion zu optimieren. Das würde auch dazu passen dass er das Programm abbricht statt dir eine StackOverflowException zu werfen.

    Wie ist denn der Callstack von dem Crash?



  • @wob sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Dein Problem ist doch, dass du zu viele Variablen in MyBigObject hast, d.h. root1, root2, root3 etc, oder?

    Nein , die MyBigObject class hat ein Root Member vom Typ X, der Type X hat Member Type Irgendwas und das in eine n-ten hierachy, d.h. ich habe ein Baumsturkt und keine flache feld hierarchy.

    Leider kan ich ein Array nicht verwenden, da die Felter (Property) konkret sein müssen. Ich mag das konzept auch nicht aber das haben leute eben so beschlossen.

    @Th69 sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    @SoIntMan: Was mir noch eingefallen ist: schau dir mal den IL-Code des Konstruktors an (z.B. mit ILSpy) und überprüfe mal, ob dort ein oder mehrere .maxstack-Anweisungen drin sind (diese sollten eigentlich nur bei lokalen Variablen und nicht bei direkten Zugriffen auf die Klassenmember benötigt werden).
    Aber probiere auch mal die Release-Version aus.

    ok das muss ich mal sehen

    @hustbaer sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Vielleicht ist auch das Problem dass der JIT nen Stack-Overflow verursacht beim Versuch die lange Funktion zu optimieren. Das würde auch dazu passen dass er das Programm abbricht statt dir eine StackOverflowException zu werfen.
    Wie ist denn der Callstack von dem Crash?

    ja das passiert ab und an.. OutOfMemory oder VS stürzt ohne meldung ab 🙂



  • @SoIntMan sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    class MyBigObject
    {
       public MyBigObject()
       {
       this.Root = new ObjectX(); //ObjectX hat ein feld A ,usw.
       this.Root.A = new ObjectY();
       this.Root.B = new ObjectZ();
      this.Root.B.A = new ObjectA();
       // und das ganze in irgendeiner form 50000 mal.
       }
    }
    

    Ich habe keine Ahnung von C#, aber wenn es ein Problem ist, dass die Source Datei zu groß ist, weil alles in MyBigObject() passiert, würde ich das verlagern, also z.B. die Variable this.Root.B.A im Konstruktor von ObjectZ() erstellen.

    @SoIntMan sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    ja das passiert ab und an.. OutOfMemory oder VS stürzt ohne meldung ab

    Vlt wäre es eine Möglichkeit schonmal VS 2022 auszuprobieren, dann mit 64 bit Unterstützung. Das könnte an der Stelle helfen.



  • 50 kloc+ in einer Funktion ist in modernen Hochsprachen einfach keine gute Idee. Quasi sämtliche Teile der Kette sind nicht darauf ausgelegt das zu verdauen, und speiben sich dementsprechend auch regelmässig an bei sowas. Der Umstand dass der Code super einfach ist, keine Schleifen enthält etc. ändert daran leider nichts.



  • Ein möglicher Workaround wäre den Code-Generator so umzustellen dass er z.B. für je 100~1000 Zeilen eine Funktion generiert, diese alle mit [MethodImpl(MethodImplOptions.NoInlining)] markiert und dann alle hintereinander im ctor aufruft.
    Kann nicht garantieren dass es hilft, aber wäre mMn. Wert dass man es ausprobiert.



  • @Schlangenmensch sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Ich habe keine Ahnung von C#, aber wenn es ein Problem ist, dass die Source Datei zu groß ist, weil alles in MyBigObject() passiert, würde ich das verlagern, also z.B. die Variable this.Root.B.A im Konstruktor von ObjectZ() erstellen.

    Das ist ein guter Vorschlag, das habe ich mir auch schon überlegt. Leider erlaubt diese das aktuellen Software design nicht, und das zu ändern ist nicht gewollt, und zudem ein mega aufwand.

    @Schlangenmensch sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    @SoIntMan sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    ja das passiert ab und an.. OutOfMemory oder VS stürzt ohne meldung ab

    Vlt wäre es eine Möglichkeit schonmal VS 2022 auszuprobieren, dann mit 64 bit Unterstützung. Das könnte an der Stelle helfen.

    ja das wäre ne Notlösung. ich sag's euch, Katastrophe die Leute da;=)

    @hustbaer sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    50 kloc+ in einer Funktion ist in modernen Hochsprachen einfach keine gute Idee. Quasi sämtliche Teile der Kette sind nicht darauf ausgelegt das zu verdauen, und speiben sich dementsprechend auch regelmässig an bei sowas. Der Umstand dass der Code super einfach ist, keine Schleifen enthält etc. ändert daran leider nichts.

    bin ganz deiner Meinung. Ich hätte das ganz anderes gemacht, aber die Urheber damals waren halt keine Architekten. Damals war das Objekt gar nich so umfangreich, aber es skaliert hat gerade gar nicht mehr. Ich würde es gern anpassen, aber das kostet zudem Geld;) und die leute kapieren es nicht das es ein großem problem is.

    @hustbaer sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Ein möglicher Workaround wäre den Code-Generator so umzustellen dass er z.B. für je 100~1000 Zeilen eine Funktion generiert, diese alle mit [MethodImpl(MethodImplOptions.NoInlining)] markiert und dann alle hintereinander im ctor aufruft.
    Kann nicht garantieren dass es hilft, aber wäre mMn. Wert dass man es ausprobiert.

    du meinst ich soll die ca 50000 zeilen in 50 funktion splitten mit dem attribute?



  • @SoIntMan sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    du meinst ich soll die ca 50000 zeilen in 50 funktion splitten mit dem attribute?

    Ja. Wenn du das ohne grossen Aufwand machen kannst, wäre das einen Versuch wert.



  • @hustbaer sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Ja. Wenn du das ohne grossen Aufwand machen kannst, wäre das einen Versuch wert.

    klar, ich kann auch jede zeile in ne funktion auslagern;) das Proble ich glatt aus;) es funktioniert momenta (gerade so am limit) aber das mache ich .. danke dir.

    Aber eine rein technische frage dazu:

    Würde ich bspw. die Zeile

    this.x = new ObjectX();
    

    n eine Function

    void Create<T>(ref T target) : where T: class, new()  { target = new T(); }
    

    dann im ctor

    Create<ObjectX>(ref this.x);
    

    kapseln, würde der Compiler dann für jeden call ein sprungbefehlt zur Funktion setzen, oder die Funktion n-mal an der stelle , ich nehme an dass du genau das mit nem notInline meinst gell.



  • @SoIntMan
    Also meine Idee war die, dass der JIT vielleicht einfach dadurch langsam wird und viel Speicher braucht, dass die Funktion so riesig ist. Wäre vorstellbar dass da Dinge passieren die nicht linear skalieren sondern schelchter. Also z.B. dass der JIT z.B. 4x länger braucht (und vielleicht auch 4x so viel Stack), wenn die Funktion 2x länger wird.

    In dem Fall kann es etwas bringen die Funktion in mehrere Stücke zu zerteilen. Nun gibt es aber - zumindest in C++ Compilern - eine Optimierung, die dann greift wenn die Funktion nur an einer einzigen Stelle aufgerufen wird. Diese werden viel eher inlined als Funktionen die an vielen Stellen aufgerufen werden. Grund ist einfach: wenn ich eine eher lange Funktion habe, und die an 100 Stellen inline, dann verschwende ich enorm Speicher. Wenn die Funktion dagegen nur an einer Stelle aufgerufen wird, dann ist es nicht wirklich wichtig wie lange die ist.
    Daher hab ich sicherheitshalber empfohlen das "no inline" Attribut dranzuschreiben. Um zu verhindern dass der JIT sieht dass die Funktion nur an der einen Stelle aufgerufen wird, und beschliesst sie zu inlinen, trotz dem sie recht gross ist. Keine Ahnung ob das passieren würde, aber ich denke ein "no inline" Attribut an der Stelle kann nicht schaden.

    Um was es mir hauptsächlich ging ist aber eher einfach zu verhindern dass so grosse Funktionen geJITed werden müssen. Und da wäre dann jede einzelne Zeile in eine Funktion zu packen wieder kontraproduktiv. Weil du dann ja wieder eine grosse Funktion brauchst die alle kleinen aufruft.

    Ob es überhaupt am JIT liegt kannst du aber relativ einfach ausprobieren. Wenn es am JIT liegt, dann sollte nur der 1. Aufruf der Funktion ein Problem sein. Weitere Aufrufe sollten dann auch ohne vergrösserten Stack gehen, und auch viel schneller laufen.



  • @hustbaer sagte in Stackoverflow bei instanzierung eine großen Objekts.:

    Ob es überhaupt am JIT liegt kannst du aber relativ einfach ausprobieren. Wenn es am JIT liegt, dann sollte nur der 1. Aufruf der Funktion ein Problem sein. Weitere Aufrufe sollten dann auch ohne vergrösserten Stack gehen, und auch viel schneller laufen.

    Also was mir eben aufgefallen ist, dass (im Debugger) wenn ich ein Breakpoint zur ersten Zeile (Funktion) setzt, und der Breakpoint erreicht wird. dann is alles in Butter und er ratterst die 50 Zeilen vollgas durch.

    Aber von CreateInstance<MyBigObject>() bis zum Breakpoint der ersten Zeile in Ctor des MyBigObject. sehe ich im Diagnose tool VS dass der Speicher auf 1,3GB ansteigt, dann absinkt auf 200MB und dann zum Breakpoint hängt.
    Das dauert 10-15sekunden , und da kann eben ein OutOfMemory kommen oder ein StackOverflow.. aber wenn er dann im ctor hängt passiert nichts mehr (außer ich würde evlt. in den 50k Zeilen zu viel Speicher allokieren dass es dann dock knallt=)

    Weißt wie ich mein?)


Log in to reply