PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C#][Unity] Hängende Referenzen finden


Baalzamon
2016-05-19, 15:02:46
Hi,

ich habe hier ein ziemliches Problem und ich bin langsam mit meinem Latein am Ende.

In unserem Projekt gibt es naturgemäß mehrere hundert Klassen und ich kann bei einer bestimmten Aktion reproduzieren, dass Instanzen nicht vom GC eingesammelt werden, ergo sie müssen noch von irgendwo referenziert werden. Ich finde aber nicht raus von wo.

Es gibt nicht viele Member-Variabeln dieses Typs und die habe ich selbstverständlich bereits alles durch und da bleibt nichts hängen.

Ich bin mit diversen Tools ran, aber der Unity-Profiler ist an dieser Stelle leider überhaupt nicht hilfreich, da er mir die Instanzen nicht im Memory-Profiler anzeigt (d.h. in der nichtssagenden Simple-View schon, aber in der Detailled-View nicht und auch nicht mit dem 'extra' Memory-Profiler den es für 5.3.x gibt).

Im Moment versuche ich es mit Reflections, bin mir aber nicht sicher ob ich damit überhaupt zum Ziel komme. Ich kann darüber die Klassen an sich checken und gucken wo es überhaupt Member dieses Typs gibt und dann die entsprechenden Klassen im Constructor die Instanz an einen Registry übergeben lassen, so dass ich zur Laufzeit gucken kann ob die Referenzen noch aktiv sind oder nicht. Aber auch das führt mich leider nicht zum Ziel, bzw. zum gewünschten Ergebnis. Die hängenden Referenzen finden sich hier nicht.

MMn kann das eigentlich nur bedeuten dass ich entweder irgendwo noch geschachtelte Generics dieses Typs habe oder Event-Beziehungen die nicht aufgelöst werden (das werde ich noch in die Überprüfung einbauen). Viel Hoffnung habe ich da allerdings nicht.

Ich bin mir echt unsicher wie man an so was überhaupt ran geht, außer das man das 'direkt ordentlich' macht, darauf habe ich leider keinen Einfluss.

Hat jemand von euch einen guten Tipp wie man so was am besten angeht? Ich kann doch unmöglich der Einzige sein der sich mit solchen Problemen rumschlägt, aber das Netz schweigt sich dazu seltsamerweise ziemlich aus.

Trap
2016-05-19, 18:33:28
Dafür nimmt man Heap-Snapshot Tools mit "Path to GC-root" Ansicht. Ich nehm meistens PerfView, aber in VS2015 könnten auch die integrierten Tools funktionieren (haben sie bei mir oft nicht).

Video wie man PerfView dafür nutzt: https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-115-PerfView-Part-3

Meistens ist es irgendwo im Framework oder einer 3rd Party Lib eine statische Liste...

Demirug
2016-05-19, 20:33:02
Unity benutzt eine sehr alte stark modifizierte Mono Runtime. Soweit mir bekannt funktioniert da keines der ganzen schönen .Net tools.

Was du versuchen kannst ist finalizer zu schreiben um zu sehen welche Objekte weggeworfen werden.

Baalzamon
2016-05-20, 09:55:25
Moin,

erstmal danke für die Antworten.

@Trap
Wie Demriug richtig angemerkt hat funktionieren die ganzen schönen Tools nicht mit Unity zusammen. Ich bin bei meiner Recherche auch auf WinDBG, sos.dll, PerfView usw. gestossen, aber nichts davon funktioniert mit Unity zusammen.

@Demirug
Danke für den finalze Ansatz, aber tatsächlich habe ich mit ziemlich großer Wahrscheinlichkeit schon rausgefunden welche Klassen hängen bleiben (über De-/Registrierung der jeweiligen Typnamen bei einem 'InstanceCounter', innerhalb des De-/Konstruktors der in Frage kommenden Klassen), aber ich finde nicht raus von wo sie referenziert werden.



Ich bin auch zuerst davon ausgegangen dass die Referenzen in einer statischen Liste oder in einer Liste in einem Singleton hängt. Die Stellen an denen die entsprechenden Klassen als Member-Variabeln (Field, Property oder einfache Generics wie Listen oder Dictionaries) deklariert sind halten sich stark in Grenzen und die offensichtlichen Sachen habe ich durch. Es gibt genau eine Liste in der die entsprechenden Instanzen gehalten werden und dort werden sie auch korrekt wieder entfernt, allerdings wird das Objekt dann nicht zerstört (natürlich nachdem ich GC.Collect() aufgerufen habe) .

Und genau da hänge ich gerade. Eigentlich kann es jetzt nur noch sein, dass die Objekte irgendwo per per Event registriert haben und sich nicht wieder abmelden oder das sie in einem verschachtelten Generic (wobei man die ja einfach über die Referenzsuche in VS finden sollte) hängen. Bleiben eigentlich nur noch Event-Registrierungen und die alle per Hand durchzugehen ist... suboptimal. Vor allen Dingen da dies zeimlich sicher nicht die einzigen hängen Referenzen im Projekt sind und ich gerne eine (halb)automatische Lösung hätte.

Deswegen auch schon mein 'verzweifelter' Ansatz mit einem eigenen Profiler auf Reflection-Basis ranzugehen. Ich habe da auch schon was, was soweit gut funktioniert aber mir die Referenzhalter trotzdem nicht auflösen kann.

Ich habe mir auch mal ProcDump angeguckt um einen Dump zu erstellen und diesen dann zu Debuggen, aber hier haut mir Unity wieder Knüppel zwischen die Beine. Ich habe es zumindest nicht hin gekriegt. Zum einen kann ich keinen Standalone-Build auf PC erstellen (Projekt ist iOS und Android) und wenn ich einen Dump vom laufenden Unity-Editor mache kann VS die Source vom Dump nicht auflösen (Symbols habe ich von http://symbolserver.unity3d.com/ importiert).

Ich bin echt ratlos. Zumal sich das Netz dazu auch ziemlich ausschweigt. Ich kann doch unmöglich der Einzige sein der sich mit diesen Problemen rumschlägt.

Exxtreme
2016-05-20, 10:09:35
Könnte auch sein, dass der GC einen Bug hat bzw. einige Dinge gar nicht findet. GC ist halt kein einfaches Thema und wenn Unity eine Uraltversion von Mono benutzt ...

AFAIR benutzte Mono mal den Boehm-Weiser-GC. Und das Ding findet definitiv nicht alles.

Baalzamon
2016-05-20, 10:39:30
Könnte auch sein, dass der GC einen Bug hat bzw. einige Dinge gar nicht findet. GC ist halt kein einfaches Thema und wenn Unity eine Uraltversion von Mono benutzt ...

AFAIR benutzte Mono mal den Boehm-Weiser-GC. Und das Ding findet definitiv nicht alles.

Die Mono-Version von Unity verwendet immer noch Boehm-Weiser-GC. Wenn das Ding nicht alles aufräumt... was soll man den da machen? Die Instanzen müssen weg.

Ich möchte wieder C++ programmieren, da hat man wenigstens die Kontrolle. :(

Exxtreme
2016-05-20, 10:44:54
Hmmm, vielleicht kannst du die Instanzen, die weg müssen mit null killen wenn sie nicht mehr gebraucht werden. Das macht man in Java auch sehr gerne auch wenn es nicht nötig ist. Kann sein, dass der GC dann das wegräumen kann.

Baalzamon
2016-05-20, 10:47:36
Hmmm, vielleicht kannst du die Instanzen, die weg müssen mit null killen wenn sie nicht mehr gebraucht werden. Das macht man in Java auch sehr gerne auch wenn es nicht nötig ist. Kann sein, dass der GC dann das wegräumen kann.
Tatsächlich mache ich das schon, wenn meine Instanzen aus der Liste fallen (was eigentlich die einzige Stelle sein sollte wo die Referenzen gehalten werden).

Da das nicht funktioniert gehe ich davon aus, dass die Instanzen noch irgendwo referenziert werden.... nur wo?

Demirug
2016-05-20, 10:49:02
Könnte auch sein, dass der GC einen Bug hat bzw. einige Dinge gar nicht findet. GC ist halt kein einfaches Thema und wenn Unity eine Uraltversion von Mono benutzt ...

AFAIR benutzte Mono mal den Boehm-Weiser-GC. Und das Ding findet definitiv nicht alles.

Ja Unity benutzt immer noch Boehm-Weiser. Für IL2CPP prüfen sie wohl gerade ob sie den von Microsoft benutzten können. Ich habe allerdings nichts dazu gesehen ob sie falls es funktioniert den auch für die "normale" Version einsetzen wollen.

Das Problem ist übrigens nicht nur der Boehm-Weiser-GC. Ich habe das Problem auch durchaus noch mit dem neuen in Xamarin(Mono. s.U.

@Baalzamon hast du das Leak auch auf dem PC oder nur auf Mobile (ARM)?

In meinem Fall (Xamarin nicht Unity; aber beides Mono) war/ist das Leak nur auf Android. Nachdem ich deswegen länger mit dem Support gesprochen haben haben sie immerhin die Ursache gefunden. Der Stackwalk in Mono für ARM ist konservative und daher nicht präzise. Er findet daher tendenziell Referencen auf Objekte die gar keine sind. In meinem Fall waren das große byte[].

Ich bin mir allerdings nicht sicher ob in der alten Mono Version die Unity benutzt der x86 stackwalk schon präzise war. Bei der neuen soll er es sein aber das habe ich nie getestet da unsere x64 version mit .Net kompiliert wird und wir daher die Microsoft Tools haben.

Baalzamon
2016-05-20, 10:52:47
@Baalzamon hast du das Leak auch auf dem PC oder nur auf Mobile (ARM)?
Das Problem tritt auf allen Endgeräten und im Unity-Editor auf.

Exxtreme
2016-05-20, 11:13:01
Da das nicht funktioniert gehe ich davon aus, dass die Instanzen noch irgendwo referenziert werden.... nur wo?
Der Boehm-Weiser-GC ist ein sog. konservativer GC. Da müssen keine Referenzen vorhanden sein, trotzdem kann es passieren, dass der GC das als Referenz ansieht. Er räumt dann nichts weg was nach einer gültigen Referenz aussieht. Von daher ... hmmm ... Pech.

Baalzamon
2016-05-20, 14:16:09
Der Boehm-Weiser-GC ist ein sog. konservativer GC. Da müssen keine Referenzen vorhanden sein, trotzdem kann es passieren, dass der GC das als Referenz ansieht. Er räumt dann nichts weg was nach einer gültigen Referenz aussieht. Von daher ... hmmm ... Pech.
Hmmm... naja, das kann doch nicht der Weisheit letzter Schluss sein? Mit diesen 'Speicherlecks' ist das Teil unspielbar und natürlich müssen die weg.

Vor allen Dingen, wenn es denn der GC sein sollte, so müsste ich das zweifelsfrei belegen können.

Demirug
2016-05-20, 15:16:43
Da du den Quellcode von Unity nicht hast wird das schwer.

Wie groß ist den so ein Objekt das dich die Leaks da killen? Oder sind es einfach sehr viele? Wenn es immer wieder der gleiche Type ist hört sich das allerdings auch mehr nach einem Fehler außerhalb des GCs an.

Hast du vielleicht irgendwo in dem Zusammenhang mit den Objekten die hängen bleiben noch Events oder Delegates? Das ist mitunter eine häufige Ursache das der GC Objekte eben nicht löscht.

Baalzamon
2016-05-20, 20:19:12
Wie groß ist den so ein Objekt das dich die Leaks da killen? Oder sind es einfach sehr viele? Wenn es immer wieder der gleiche Type ist hört sich das allerdings auch mehr nach einem Fehler außerhalb des GCs an.
Das ist ja die Krux, das sind in einer bestimmten Situation schon hunderte von Instanzen die dann zusammen auch schon mal mehrere dutzend MB ausmachen. Das Tragische ist, dass man den Vorgang wiederholen kann (und das auch durchaus so sein soll, dass der Spieler das öfter in einer Session macht) und man damit sehr schnell das Speicherlimit eines mobilen Endgeräts erreicht und sich die App dann einfach beendet.

Hast du vielleicht irgendwo in dem Zusammenhang mit den Objekten die hängen bleiben noch Events oder Delegates? Das ist mitunter eine häufige Ursache das der GC Objekte eben nicht löscht.
Ja, das vermute ich inzwischen auch. Aber den ganzen Code per Hand durchwühlen.... das muss doch auch besser gehen. :(

Demirug
2016-05-20, 21:36:37
Das ist ja die Krux, das sind in einer bestimmten Situation schon hunderte von Instanzen die dann zusammen auch schon mal mehrere dutzend MB ausmachen. Das Tragische ist, dass man den Vorgang wiederholen kann (und das auch durchaus so sein soll, dass der Spieler das öfter in einer Session macht) und man damit sehr schnell das Speicherlimit eines mobilen Endgeräts erreicht und sich die App dann einfach beendet.

Kommt mir bekannt vor: https://play.google.com/store/apps/details?id=com.nexonm.pathofwar

Ich habe halt wie gesagt den Luxus das wir Xamarin und nicht Unity genommen haben. Zudem haben wir eine .net Desktop Version bei der ich Memory dumps ziehen kann.

Ja, das vermute ich inzwischen auch. Aber den ganzen Code per Hand durchwühlen.... das muss doch auch besser gehen. :(

Ich nehme mal an das der Codeblock zu stark mit der Unity API verknüpft ist um in mal in einer .Net Anwendung laufen zu lassen.

myMind
2016-05-20, 21:48:48
Die drei schlimmsten Bugs, die ich in der Richtung mal hatte waren:
- Objekte landeten auf dem Large Object Heap und wurden damals deshalb nicht weggeräumt. Das war noch .NET 1.1. Also gar kein Programmierproblem, sondern der Runtime geschuldet.
- Bug in einem Grid-Control vom MS. Das Control hatte eine interne Liste, in der es die Datenobjekte gesammelt hat und beim Entferenen nicht removed hatte. Mit dem damals noch kostenlosen .NET Reflector von redgate, konnte man das Problem im decompilierten Code von MS sogar sehen.
- Bug in einem Command-Objekt (auch im verwendeten Framework). Es hätte an einer Stelle eine Weak-Reference in Richtung GUI verwendet werden müssen.

Ein Blick in die Buglisten / Tracker der verwendeten Frameworks kann nicht schaden. Ggf. ist das Problem schon bekannt. Es kann sein, dass es nicht in deinem Code steckt.

Bei der Lokalisierierung der Probleme hat uns der .NET Memory Profiler (http://memprofiler.com/) damals gute Dienste geleistet. Aber die betroffene Klasse hast Du ja schon gefunden. Das ist doch schonmal nicht schlecht.

Wenn alle offensichtlichen Methoden wie genaues Hinschauen, Debuggen oder Testcode die Problemursache nicht aufdecken können, dann hat bei uns hat die Rückbaumethode jeweils zum Ziel geführt.

Wichtig dabei ist, dass man das Problem mit überschaubarem Aufwand, möglichst automatisch, nachstellen kann. Z.B. mit einem Dauerlauftest.

Idealerweise hast Du dann einen Testfall in dem möglichst viele Funktionen die im Moment unwichtig sind weggemockt sind und der Fehler trotzdem auftritt. Ansonsten tuts halt auch die ganze Anwendung, ist aber noch aufwendiger.

Als nächstes würde ich die Anwendung Stück für Stück zurückbauen. Hier zahlt sich jetzt jede Investition in gute Strukturen aus. Dabei die Stände im Git/Svn merken. Also immer ganz Stumpf: Rückbau - Test - Rückbau - Test - Rückbau - usw. Tritt der Fehler plötzlich nicht mehr auf, hast Du wohl den kaputten Teil erwischt. So etwas kann dauern. Für den oben genannten 2. Fall habe ich drei Wochen gebraucht, um das Problem zu lokalisieren. Der Kunde war aber megaglücklich.

Wenn man das Problem nicht selbst exakt herauskriegt, sollte man zumindest, mit einer überschaubare Menge an Code dastehen in der das Problem auftritt und den man dann dem Frameworkhersteller in die Hand drücken kann.

Ich drücke die Daumen.

SimonX
2016-05-24, 07:59:27
Die Mono-Version von Unity verwendet immer noch Boehm-Weiser-GC. Wenn das Ding nicht alles aufräumt... was soll man den da machen? Die Instanzen müssen weg.

Ich möchte wieder C++ programmieren, da hat man wenigstens die Kontrolle. :(

Du kannst immer deinen eigenen Destructor schreiben und ihn wie bei einem C++ delete explizit aufrufen.

Der Destructur kann wohl das Objekt nicht zerstören, aber du kannst das Objekt durch das Zurücksetzen aller Member-Referenzen aktiv aufräumen.

][immy
2016-05-27, 13:49:17
Es wäre vielleicht ganz gut zu wissen was diese Klasse macht und wofür sie verantwortlich ist.

Um mal ganz billig anzufangen, du hast nicht zufällig irgendwo eine "endlosschleife" in einem Thread hängen? Innerhalb einer Methode würden genutzten Referenzen ja nicht vom GC abgeräumt, sondern erst nach verlassen der jeweiligen Methode.
Ich schätze mal das dies hier unwahrscheinlich bei dir ist, aber manchmal sieht man ja den Wald vor lauter Bäumen nicht mehr.

Events & Delegates sind natürlich auch beliebte "Probleme" wenn es um vorhandene Referenzen geht.
Unter normalen Umständen würde ich dir die Redgate-Tools empfehlen, mit denen ich schon so einige Schwachstellen aufdecken konnte, aber die werden wohl mit Mono/Unity nicht so recht funktionieren wollen.

Baalzamon
2016-10-13, 16:41:24
Besser spät als nie....

Vielen Dank für eure Vorschläge. Im Endeffekt habe ich es mit Fleiß und viel Handarbeit gelöst und den Schuldigen in unserem Event Management System ausgemacht.

Es waren tatsächlich nicht deregeistrierte Events die die Zerstörung der Instanzen verhindert hat. Leider war das alles unter Schichten von unlesbarem Code vergraben, so dass es nicht wirklich offensichtlich war... also eigentlich bin ich da nur drauf gestossen weil ich den ganzen Scheiß dutzende Male von Hand durchgegangen bin. :(

Die Lösung ist im Detail hier wohl nicht so interessant, aber immerhin habe ich es irgendwann gelöst bekommen.