Dynamisches Detail: verdeckte Compiler-Spiele, Speicherverlust, Leistungsnuancen

Vorspiel



Betrachten Sie den folgenden Code:

//Any native COM object var comType = Type.GetTypeFromCLSID(new Guid("E13B6688-3F39-11D0-96F6-00A0C9191601")); while (true) { dynamic com = Activator.CreateInstance(comType); //do some work Marshal.FinalReleaseComObject(com); } 


Die Signatur der Marshal.FinalReleaseComObject- Methode lautet wie folgt:

 public static int FinalReleaseComObject(Object o) 


Wir erstellen ein einfaches COM-Objekt, erledigen einige Arbeiten und geben es sofort frei. Es scheint, dass was schief gehen könnte? Ja, das Erstellen eines Objekts in einer Endlosschleife ist keine gute Vorgehensweise, aber der GC übernimmt die gesamte Drecksarbeit. Die Realität sieht etwas anders aus:



Um zu verstehen, warum Speicherlecks auftreten, müssen Sie verstehen, wie Dynamik funktioniert. Es gibt bereits mehrere Artikel zu diesem Thema über Habré, zum Beispiel diesen , aber sie gehen nicht auf Details der Implementierung ein, daher werden wir unsere eigenen Forschungen durchführen.



Zuerst werden wir den dynamischen Arbeitsmechanismus im Detail untersuchen, dann werden wir das erworbene Wissen auf ein einziges Bild reduzieren und am Ende werden wir die Gründe für dieses Leck diskutieren und wie es vermieden werden kann. Lassen Sie uns vor dem Eintauchen in den Code die Quelldaten klären: Welche Kombination von Faktoren führt zum Leck?

Die Experimente



Vielleicht ist das Erstellen vieler nativer COM- Objekte an sich eine schlechte Idee? Lassen Sie uns überprüfen:

 //Any native COM object var comType = Type.GetTypeFromCLSID(new Guid("E13B6688-3F39-11D0-96F6-00A0C9191601")); while (true) { dynamic com = Activator.CreateInstance(comType); } 


Diesmal ist alles gut:



Kehren wir zur ursprünglichen Version des Codes zurück, ändern Sie jedoch den Objekttyp:

 //Any managed type include managed COM var type = typeof(int); while (true) { dynamic com = Activator.CreateInstance(type); //do some work Marshal.FinalReleaseComObject(com); } 


Und wieder keine Überraschungen:



Versuchen wir die dritte Option:

 //Simple COM object var comType = Type.GetTypeFromCLSID(new Guid("435356F9-F33F-403D-B475-1E4AB512FF95")); while (true) { dynamic com = Activator.CreateInstance(comType); //do some work Marshal.FinalReleaseComObject((object) com); } 


Nun, wir sollten definitiv das gleiche Verhalten bekommen! Huh? Nein :(



Ein ähnliches Bild wird angezeigt, wenn Sie com als Objekt deklarieren oder mit Managed COM arbeiten . Fassen Sie die experimentellen Ergebnisse zusammen:

  1. Das Instanziieren nativer COM- Objekte selbst führt nicht zu Lecks - GC bewältigt erfolgreich das Löschen des Speichers
  2. Bei der Arbeit mit einer verwalteten Klasse treten keine Lecks auf
  3. Wenn Sie ein Objekt explizit in ein Objekt umwandeln , ist auch alles in Ordnung


Mit Blick auf die Zukunft können wir zum ersten Punkt hinzufügen, dass das Arbeiten mit dynamischen Objekten (Aufrufen von Methoden oder Arbeiten mit Eigenschaften) für sich genommen ebenfalls keine Lecks verursacht. Die Schlussfolgerung bietet sich an: Ein Speicherverlust tritt auf, wenn wir ein dynamisches Objekt (ohne "manuelle" Typkonvertierung) übergeben, das natives COM als Methodenparameter enthält.

Wir müssen tiefer gehen



Es ist Zeit, sich daran zu erinnern, worum es bei dieser Dynamik geht :

Kurzanleitung
C # 4.0 bietet eine neue Art von Dynamik . Dieser Typ vermeidet die statische Typprüfung durch den Compiler. In den meisten Fällen funktioniert es als Objekttyp . Bei der Kompilierung wird davon ausgegangen, dass ein als dynamisch deklariertes Element jede Operation unterstützt. Dies bedeutet, dass Sie nicht darüber nachdenken müssen, woher das Objekt stammt - von der COM-API, einer dynamischen Sprache wie IronPython, mithilfe von Reflection oder von einem anderen Ort. Wenn der Code ungültig ist, werden außerdem zur Laufzeit Fehler ausgegeben.

Wenn beispielsweise die exampleMethod1- Methode im folgenden Code genau einen Parameter enthält, erkennt der Compiler, dass der erste Aufruf der ec.exampleMethod1 (10, 4) -Methode ungültig ist, da sie zwei Parameter enthält. Dies führt zu einem Kompilierungsfehler. Der zweite Methodenaufruf, dynamic_ec.exampleMethod1 (10, 4), wird vom Compiler nicht geprüft, da dynamic_ec daher als dynamisch deklariert wird. Es werden keine Kompilierungsfehler angezeigt. Trotzdem bleibt der Fehler nicht für immer unbemerkt - er wird zur Laufzeit erkannt.

 static void Main(string[] args) { ExampleClass ec = new ExampleClass(); //      ,  exampleMethod1    . //ec.exampleMethod1(10, 4); dynamic dynamic_ec = new ExampleClass(); //      ,  //      dynamic_ec.exampleMethod1(10, 4); //        ,  //  ,      dynamic_ec.someMethod("some argument", 7, null); dynamic_ec.nonexistentMethod(); } 


 class ExampleClass { public ExampleClass() { } public ExampleClass(int v) { } public void exampleMethod1(int i) { } public void exampleMethod2(string str) { } } 




Code, der dynamische Variablen verwendet, wird während der Kompilierung erheblich geändert. Dieser Code:

 dynamic com = Activator.CreateInstance(comType); Marshal.FinalReleaseComObject(com); 


Wird zu folgendem:

 object instance = Activator.CreateInstance(typeFromClsid); // ISSUE: reference to a compiler-generated field if (Foo.o__0.p__0 == null) { // ISSUE: reference to a compiler-generated field Foo.o__0.p__0 = CallSite<Action<CallSite, Type, object>>.Create(Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "FinalReleaseComObject", (IEnumerable<Type>) null, typeof (Foo), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null) })); } // ISSUE: reference to a compiler-generated field // ISSUE: reference to a compiler-generated field Foo.o__0.p__0.Target((CallSite) Foo.o__0.p__0, typeof (Marshal), instance); 


Dabei ist o__0 die generierte statische Klasse und p__0 das statische Feld darin:

 private class o__0 { public static CallSite<Action<CallSite, Type, object>> p__0; } 


Hinweis: Für jede Interaktion mit Dynamic wird ein CallSite-Feld erstellt. Dies ist, wie später zu sehen sein wird, erforderlich, um die Leistung zu optimieren.

Beachten Sie, dass keine Erwähnung von Dynamik übrig bleibt - unser Objekt wird jetzt in einer Variablen vom Typ Objekt gespeichert. Lassen Sie uns den generierten Code durchgehen. Zunächst wird eine Bindung erstellt, die beschreibt, was und was wir tun:

 Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "FinalReleaseComObject", (IEnumerable<Type>) null, typeof (Foo), (IEnumerable<CSharpArgumentInfo>) new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, (string) null) }) 


Dies ist eine Beschreibung unseres dynamischen Betriebs. Ich möchte Sie daran erinnern, dass wir eine dynamische Variable an die FinalReleaseComObject- Methode übergeben.

  • CSharpBinderFlags.ResultDiscarded - Das Ergebnis der Methodenausführung wird in Zukunft nicht mehr verwendet
  • "FinalReleaseComObject" - der Name der aufgerufenen Methode
  • typeof (Foo) - Operationskontext; die Art des Anrufs


CSharpArgumentInfo - Beschreibung der Bindungsparameter. In unserem Fall:

  • CSharpArgumentInfo.Create (CSharpArgumentInfoFlags.UseCompileTimeType | CSharpArgumentInfoFlags.IsStaticType, (string) null) - Beschreibung des ersten Parameters - der Marshal-Klasse: Es ist statisch und sein Typ sollte beim Binden berücksichtigt werden
  • CSharpArgumentInfo.Create (CSharpArgumentInfoFlags.None, (string) null) - Beschreibung des Methodenparameters, normalerweise gibt es keine zusätzlichen Informationen.


Wenn es nicht darum geht, eine Methode aufzurufen, sondern beispielsweise eine Eigenschaft von einem dynamischen Objekt aus aufzurufen , gibt es nur eine CSharpArgumentInfo , die das dynamische Objekt selbst beschreibt.

CallSite ist ein Wrapper über einen dynamischen Ausdruck. Es enthält zwei wichtige Felder für uns:

  • öffentliches T-Update
  • öffentliches T-Ziel


Aus dem generierten Code geht hervor, dass Target bei jeder Operation mit Parametern aufgerufen wird, die es beschreiben:

 Foo.o__0.p__0.Target((CallSite) Foo.o__0.p__0, typeof (Marshal), instance); 


In Verbindung mit der oben beschriebenen CSharpArgumentInfo bedeutet dieser Code Folgendes: Sie müssen die FinalReleaseComObject-Methode für die statische Marshal-Klasse mit dem Instanzparameter aufrufen. Zum Zeitpunkt des ersten Aufrufs wird in Target derselbe Delegat gespeichert wie in Update . Der Update- Delegierte ist für zwei wichtige Aufgaben verantwortlich:

  1. Binden einer dynamischen Operation an eine statische (der Biding-Mechanismus selbst geht über den Rahmen dieses Artikels hinaus)
  2. Cache-Bildung


Wir interessieren uns für den zweiten Punkt. Hierbei ist zu beachten, dass bei der Arbeit mit einem dynamischen Objekt jedes Mal die Gültigkeit der Operation überprüft werden muss. Dies ist eine ziemlich ressourcenintensive Aufgabe, daher möchte ich die Ergebnisse solcher Überprüfungen zwischenspeichern. Beim Aufrufen einer Methode mit einem Parameter müssen wir Folgendes beachten:

  1. Der Typ, für den die Methode aufgerufen wird
  2. Der Objekttyp, der vom Parameter übergeben wird (um sicherzustellen, dass er in den Typ des Parameters umgewandelt werden kann).
  3. Ist die Operation gültig?


Wenn Sie Target erneut aufrufen, müssen Sie keine relativ teuren Bindungen ausführen: Vergleichen Sie einfach die Typen und rufen Sie, wenn sie übereinstimmen, die Zielfunktion auf. Um dieses Problem zu lösen, wird für jede dynamische Operation ein ExpressionTree erstellt, in dem die Einschränkungen und die Zielfunktion gespeichert sind, an die der dynamische Ausdruck gebunden wurde.

Es gibt zwei Arten von Funktionen:

  • Bindungsfehler : Beispielsweise wird eine Methode für ein dynamisches Objekt aufgerufen, das nicht vorhanden ist, oder ein dynamisches Objekt kann nicht in den Typ des Parameters konvertiert werden, an den es übergeben wird. Anschließend müssen Sie eine Ausnahme wie Microsoft.CSharp.RuntimeBinderException: 'NoSuchMember' auslösen.
  • Die Herausforderung ist legal: Führen Sie dann einfach die erforderliche Aktion aus


Dieser ExpressionTree wird während der Ausführung des Update- Delegaten gebildet und in Target gespeichert. Ziel - L0- Cache, wir werden später mehr über den Cache sprechen.

Daher speichert Target den letzten ExpressionTree , der durch den Update- Delegaten generiert wurde. Lassen Sie uns sehen, wie diese Regel wie ein Beispiel für einen verwalteten Typ aussieht, der an die Boo- Methode übergeben wird:

 public class Foo { public void Test() { var type = typeof(int); dynamic instance = Activator.CreateInstance(type); Boo(instance); } public void Boo(object o) { } } 


 .Lambda CallSite.Target<System.Action`3[Actionsss.CallSite,ConsoleApp12.Foo,System.Object]>( Actionsss.CallSite $$site, ConsoleApp12.Foo $$arg0, System.Object $$arg1) { .Block() { .If ($$arg0 .TypeEqual ConsoleApp12.Foo && $$arg1 .TypeEqual System.Int32) { .Return #Label1 { .Block() { .Call $$arg0.Boo((System.Object)((System.Int32)$$arg1)); .Default(System.Object) } } } .Else { .Default(System.Void) }; .Block() { .Constant<Actionsss.Ast.Expression>(IIF((($arg0 TypeEqual Foo) AndAlso ($arg1 TypeEqual Int32)), returnUnamedLabel_0 ({ ... }) , default(Void))); .Label .LabelTarget CallSiteBinder.UpdateLabel: }; .Label .If ( .Call Actionsss.CallSiteOps.SetNotMatched($$site) ) { .Default(System.Void) } .Else { .Invoke (((Actionsss.CallSite`1[System.Action`3[Actionsss.CallSite,ConsoleApp12.Foo,System.Object]])$$site).Update)( $$site, $$arg0, $$arg1) } .LabelTarget #Label1: } } 


Der wichtigste Block für uns:

 .If ($$arg0 .TypeEqual ConsoleApp12.Foo && $$arg1 .TypeEqual System.Int32) 


$$ arg0 und $$ arg1 sind die Parameter, mit denen Target aufgerufen wird:
 Foo.o__0.p__0.Target((CallSite) Foo.o__0.p__0, <b>this</b>, <b>instance</b>); 


In menschlich übersetzt bedeutet dies Folgendes:

Wir haben bereits überprüft, dass Sie Boo ((Objekt) $$ arg1) sicher aufrufen können, wenn der erste Parameter vom Typ Foo und der zweite Int32 ist.

 .Return #Label1 { .Block() { .Call $$arg0.Boo((System.Object)((System.Int32)$$arg1)); .Default(System.Object) } 


Hinweis: Im Falle eines Bindungsfehlers sieht der Label1-Block ungefähr so ​​aus:
 .Return #Label1 { .Throw .New Microsoft.CSharp.RuntimeBinderException("NoSuchMember") 


Diese Prüfungen werden als Einschränkungen bezeichnet . Es gibt zwei Arten von Einschränkungen : nach Objekttyp und nach spezifischer Instanz des Objekts (das Objekt muss genau gleich sein). Wenn mindestens eine der Einschränkungen fehlschlägt, müssen wir den dynamischen Ausdruck erneut auf Gültigkeit überprüfen. Dazu rufen wir den Update- Delegaten auf. Nach dem uns bereits bekannten Schema wird er die Bindung mit neuen Typen durchführen und den neuen ExpressionTree in Target speichern.

Cache



Wir haben bereits herausgefunden, dass Target ein L0-Cache ist . Jedes Mal, wenn Target aufgerufen wird, gehen wir zuerst die bereits darin gespeicherten Einschränkungen durch. Wenn die Einschränkungen fehlschlagen und eine neue Bindung generiert wird, geht die alte Regel gleichzeitig zu L1 und L2 . Wenn Sie in Zukunft den L0- Cache verpassen, werden die Regeln von L1 und L2 durchsucht, bis eine geeignete gefunden wird.

  • L1 : Die letzten zehn Regeln, die L0 verlassen haben (direkt in CallSite gespeichert)
  • L2 : Die letzten 128 Regeln, die mit einer bestimmten Binder-Instanz erstellt wurden ( CallSiteBinder , die für jede CallSite eindeutig ist).


Jetzt können wir diese Details endlich zu einem Ganzen hinzufügen und in Form eines Algorithmus beschreiben, was passiert, wenn Foo.Bar (someDynamicObject) aufgerufen wird :

1. Es wird ein Ordner erstellt, der den Kontext und die aufgerufene Methode auf der Ebene ihrer Signaturen speichert

2. Beim ersten Aufruf der Operation wird ExpressionTree erstellt, in dem Folgendes gespeichert wird:
2.1 Einschränkungen . In diesem Fall sind dies zwei Einschränkungen für die Art der aktuellen Bindungsparameter
2.2 Zielfunktion : entweder eine Ausnahme auslösen (in diesem Fall ist dies unmöglich, da jede Dynamik erfolgreich zum Objekt führt) oder einen Aufruf der Bar- Methode

3. Kompilieren Sie den resultierenden ExpressionTree und führen Sie ihn aus

4. Wenn Sie sich an die Operation erinnern, sind zwei Optionen möglich:
4.1 Einschränkungen funktionierten : Rufen Sie einfach Bar an
4.2 Einschränkungen haben nicht funktioniert : Wiederholen Sie Schritt 2 für neue Bindungsparameter

Am Beispiel des verwalteten Typs wurde also ungefähr klar, wie dynamisch von innen funktioniert. Im beschriebenen Fall wird der Cache nie übersehen, da die Typen immer gleich sind *. Daher wird Update genau einmal aufgerufen, wenn CallSite initialisiert wird. Dann werden für jeden Anruf nur Einschränkungen überprüft und die Zielfunktion wird sofort aufgerufen. Dies stimmt hervorragend mit unseren Beobachtungen des Gedächtnisses überein: keine Berechnung - keine Lecks.

* Aus diesem Grund generiert der Compiler seine CallSites für jede einzelne: Die Wahrscheinlichkeit, dass der L0-Cache fehlt, ist extrem reduziert

Es ist Zeit herauszufinden, wie sich dieses Schema bei nativen COM- Objekten unterscheidet. Werfen wir einen Blick auf ExpressionTree :

 .Lambda CallSite.Target<System.Action`3[Actionsss.CallSite,ConsoleApp12.Foo,System.Object]>( Actionsss.CallSite $$site, ConsoleApp12.Foo $$arg0, System.Object $$arg1) { .Block() { .If ($$arg0 .TypeEqual ConsoleApp12.Foo && .Block(System.Object $var1) { $var1 = .Constant<System.WeakReference>(System.WeakReference).Target; $var1 != null && (System.Object)$$arg1 == $var1 }) { .Return #Label1 { .Block() { .Call $$arg0.Boo((System.__ComObject)$$arg1); .Default(System.Object) } } } .Else { .Default(System.Void) }; .Block() { .Constant<Actionsss.Ast.Expression>(IIF((($arg0 TypeEqual Foo) AndAlso {var Param_0; ... }), returnUnamedLabel_1 ({ ... }) , default(Void))); .Label .LabelTarget CallSiteBinder.UpdateLabel: }; .Label .If ( .Call Actionsss.CallSiteOps.SetNotMatched($$site) ) { .Default(System.Void) } .Else { .Invoke (((Actionsss.CallSite`1[System.Action`3[Actionsss.CallSite,ConsoleApp12.Foo,System.Object]])$$site).Update)( $$site, $$arg0, $$arg1) } .LabelTarget #Label1: } } 


Es ist ersichtlich, dass der Unterschied nur in der zweiten Einschränkung liegt:

 .If ($$arg0 .TypeEqual ConsoleApp12.Foo && .Block(System.Object $var1) { $var1 = .Constant<System.WeakReference>(System.WeakReference).Target; $var1 != null && (System.Object)$$arg1 == $var1 }) 


Wenn wir im Fall von verwaltetem Code zwei Einschränkungen für den Objekttyp hatten, sehen wir hier, dass die zweite Einschränkung die Äquivalenz von Instanzen durch WeakReference überprüft .

Hinweis: Die Instanzbeschränkung wird zusätzlich zu COM-Objekten auch für TransparentProxy verwendet

In der Praxis bedeutet dies, basierend auf unserem Wissen über die Funktionsweise des Caches, dass jedes Mal, wenn wir ein COM- Objekt in einer Schleife neu erstellen, der L0- Cache (und auch L1 / L2) übersehen wird, da die alten Regeln mit Links dort gespeichert werden zu alten Instanzen). Die erste Annahme, die Sie im Kopf fragt, ist, dass der Regel-Cache fließt. Aber der Code dort ist recht einfach und dort ist alles in Ordnung: Die alten Regeln werden korrekt gelöscht. Gleichzeitig verhindert die Verwendung von WeakReference in ExpressionTree nicht, dass der GC unnötige Objekte sammelt.

Der Mechanismus zum Speichern von Regeln im L1-Cache:

 const int MaxRules = 10; internal void AddRule(T newRule) { T[] rules = Rules; if (rules == null) { Rules = new[] { newRule }; return; } T[] temp; if (rules.Length < (MaxRules - 1)) { temp = new T[rules.Length + 1]; Array.Copy(rules, 0, temp, 1, rules.Length); } else { temp = new T[MaxRules]; Array.Copy(rules, 0, temp, 1, MaxRules - 1); } temp[0] = newRule; Rules = temp; } 


Also, was ist los? Versuchen wir, die Hypothese zu klären: Beim Binden eines COM- Objekts tritt irgendwo ein Speicherverlust auf.

Experimente, Teil 2



Gehen wir noch einmal von spekulativen Schlussfolgerungen zu Experimenten über. Wiederholen wir zunächst, was der Compiler für uns tut:

 //Simple COM object var comType = Type.GetTypeFromCLSID(new Guid("435356F9-F33F-403D-B475-1E4AB512FF95")); var autogeneratedBinder = Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Boo", null, typeof(Foo), new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create( CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var callSite = CallSite<Action<CallSite, Foo, object>>.Create(autogeneratedBinder); while (true) { object instance = Activator.CreateInstance(comType); callSite.Target(callSite, this, instance); } 


Wir prüfen:



Das Leck ist erhalten geblieben. Fair. Aber was ist der Grund? Nach dem Studium des Codes der Ordner (die wir in Klammern lassen) ist klar, dass das einzige, was den Typ unseres Objekts beeinflusst, die Einschränkungsoption ist. Vielleicht handelt es sich nicht um COM- Objekte, sondern um einen Ordner? Es gibt nicht viel Auswahl. Lassen Sie uns für den verwalteten Typ eine Mehrfachbindung provozieren:

 while (true) { object instance = Activator.CreateInstance(typeof(int)); var autogeneratedBinder = Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded, "Boo", null, typeof(Foo), new CSharpArgumentInfo[2] { CSharpArgumentInfo.Create( CSharpArgumentInfoFlags.UseCompileTimeType, null), CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }); var callSite = CallSite<Action<CallSite, Foo, object>>.Create(autogeneratedBinder); callSite.Target(callSite, this, instance); } 




Wow! Es scheint, wir haben ihn gefangen. Das Problem liegt überhaupt nicht beim COM-Objekt , wie es uns anfangs erschien, nur aufgrund der Einschränkungen der Instanz ist dies der einzige Fall, in dem die Bindung innerhalb unserer Schleife viele Male auftritt. In allen anderen Fällen habe ich den L0-Cache aufgerufen und einmal gebunden.

Schlussfolgerungen



Speicherverlust



Wenn Sie mit dynamischen Variablen arbeiten, die natives COM oder TransparentProxy enthalten , übergeben Sie diese niemals als Methodenparameter. Wenn Sie dies dennoch tun müssen, verwenden Sie die explizite Umwandlung zum Objekt, und der Compiler bleibt hinter Ihnen zurück

Falsch :
 dynamic com = Activator.CreateInstance(comType); //do some work Marshal.FinalReleaseComObject(com); 


Richtig :
 dynamic com = Activator.CreateInstance(comType); //do some work Marshal.FinalReleaseComObject((object) com); 


Versuchen Sie als zusätzliche Vorsichtsmaßnahme, solche Objekte so selten wie möglich zu instanziieren. Tatsächlich für alle Versionen von .NET Framework . (Fürs Erste) ist nicht sehr relevant für. NET Core , da dynamische COM- Objekte nicht unterstützt werden.

Leistung



Es liegt in Ihrem Interesse, dass Cache-Fehler so selten wie möglich auftreten, da in diesem Fall in Caches auf hoher Ebene keine geeignete Regel gefunden werden muss. Fehler im L0- Cache treten hauptsächlich bei einer Nichtübereinstimmung des Typs des dynamischen Objekts auf, wobei die Einschränkungen beibehalten werden.

 dynamic com = GetSomeObject(); public object GetSomeObject() { //:      //:         } 


In der Praxis werden Sie den Leistungsunterschied jedoch wahrscheinlich nicht bemerken, es sei denn, die Anzahl der Aufrufe dieser Funktion wird in Millionen gemessen oder die Variabilität der Typen ist nicht ungewöhnlich groß. Die Kosten im Falle eines Fehlschlags im L0- Cache sind solche, N ist die Anzahl der Typen:

  • N <10. Wenn Sie dies verpassen, durchlaufen Sie nur die vorhandenen L1- Cache-Regeln
  • 10 < N <128 . Aufzählung des L1- und L2- Cache (maximal 10 und N Iterationen). Erstellen und Auffüllen eines Arrays mit 10 Elementen
  • N > 128. Iterieren Sie über den L1- und L2- Cache. Erstellen und füllen Sie Arrays mit 10 und 128 Elementen. Wenn Sie den L2- Cache verpassen, binden Sie ihn erneut


Im zweiten und dritten Fall erhöht sich die Belastung des GC.

Fazit



Leider haben wir keinen wirklichen Grund für den Speicherverlust gefunden. Dies erfordert eine separate Untersuchung des Binders. Glücklicherweise gibt WinDbg einen Hinweis für weitere Untersuchungen: Im DLR passiert etwas Schlimmes. Die erste Spalte gibt die Anzahl der Objekte an



Bonus



Warum verhindert das Gießen auf ein Objekt explizit ein Leck?
Jeder Typ kann in ein Objekt umgewandelt werden , sodass die Operation nicht mehr dynamisch ist.

Warum gibt es keine Lecks bei der Arbeit mit Feldern und Methoden eines COM-Objekts?
So sieht ExpressionTree für den Feldzugriff aus:

 .If ( .Call System.Dynamic.ComObject.IsComObject($$arg0) ) { .Return #Label1 { .Dynamic GetMember ComMarks(.Call System.Dynamic2.ComObject.ObjectToComObject($$arg0)) } } 

Source: https://habr.com/ru/post/de466657/


All Articles