2011-01-17 6 views
1

Mit zwei ähnlichen Anwendungen: die eine verwaltet und die andere nicht gemanaged. Beide verwenden ein schweres Zuweisungsmuster für große Objekte. I.e. Sie fordern viele dieser Objekte in einer (lang laufenden) Schleife an und beide geben diese Objekte nach der Verwendung sofort frei. Die verwaltete App verwendet IDisposable(), die sofort aufgerufen wird. Das Unmanaged benutzt die Destruktoren.Wer profitiert mehr vom Pooling? verwaltet/nicht verwaltet?

Einige, aber nicht alle Objekte konnten wiederverwendet werden. Daher wird ein Pool in Betracht gezogen, um die Ausführungsgeschwindigkeit zu erhöhen und das Risiko einer Speicherfragmentierung zu minimieren.

Welche Anwendung würden Sie mehr von einem Pooling profitieren und warum?

@Update: Dies ist eine mathematische Bibliothek. Daher sind diese großen Objekte Arrays vom Werttyp. Meistens groß genug für LOH. Ich bin positiv, Pooling würde die Performance auf der verwalteten Seite sehr verbessern. Viele Bibliotheken existieren - für verwaltete/nicht verwaltete Umgebungen. Keiner von denen, die ich kenne, macht solche Pooling wirklich. Ich frage mich warum?

+0

Ist das eine Hausaufgabe? –

+0

Ich wünschte, es wäre. Aber es ist nur ein Diskussionsthema, das wir nicht loswerden können ... Ich möchte hier keine möglichen Antworten geben, aber ich habe sicherlich eine Idee, von der ich hoffe, dass sie bestätigt oder widerlegt wird. Sollte auch helfen, den Überblick über den Kontext zu bekommen. – user492238

+2

Afaik Pooling ist sehr wichtig für große verwaltete Objekte. – CodesInChaos

Antwort

1

seltsam fühlt, aber ich werde versuchen, es selbst zu beantworten. Darf ich einige Kommentare dazu bekommen:

Beide Plattformen leiden unter Fragmentierung, wenn sehr große Objekte auf eine schwere Art und Weise zugewiesen und freigegeben werden (Schleifen). Im Fall von unmanaged Apps werden Zuweisungen direkt aus dem virtuellen Adressraum vorgenommen. Normalerweise wird das Working-Array in eine Klasse (C++) gehüllt, was Operatorüberladungen für eine kurze Syntax, einige Referenzbehandlungen und einen Destruktor liefert, der sicherstellt, dass das Array sofort freigegeben wird, wenn der Scope nicht mehr reicht. Dennoch haben die angeforderten Arrays nicht immer die gleiche Größe - wenn größere Arrays angefordert werden, kann derselbe Adressblock nicht wiederverwendet werden, was im Laufe der Zeit zu einer Fragmentierung führen kann. Außerdem gibt es keine Möglichkeit, den Block zu finden, der genau der angeforderten Array-Länge dient. Das Betriebssystem würde einfach den ersten Block verwenden, der groß genug ist - auch wenn er bei Bedarf größer ist und möglicherweise die Anforderung für ein später ankommendes, noch größeres Array hätte besser erfüllen können. Wie könnte das Pooling diese Situation verbessern?

Vorstellbar wäre, größere Arrays für kleinere Anfragen zu verwenden. Die Klasse würde den Übergang von der wahren Länge des zugrundeliegenden Arrays zu der für die Außenwelt benötigten virtuellen Länge handhaben. Der Pool könnte helfen, das "erste Array, das lang genug ist" zu liefern - im Gegensatz zum OS, das immer die genaue Länge angibt. Dies könnte möglicherweise die Fragmentierung begrenzen, da weniger Löcher im virtuellen Adressraum erzeugt werden. Auf der anderen Seite würde die Gesamtspeichergröße zunehmen. Für fast zufällige Zuteilungsmuster würde Pooling wenig bis keinen Gewinn bringen, aber nur selten Speicher essen, denke ich.

Auf der verwalteten Seite ist die Situation schlimmer. Zuallererst gibt es zwei mögliche Ziele für die Fragmentierung: den virtuellen Adressraum und den verwalteten großen Objekt-Heap. Letzter wäre in diesem Fall mehr eine Sammlung von einzelnen Folgen, individuell aus dem OS zugeordnet. Jede Sequenz würde meistens nur für ein einzelnes Array verwendet werden (da wir hier von wirklich großen Arrays sprechen). Wenn ein Array vom GC freigegeben wird, wird das gesamte Segment an das Betriebssystem zurückgegeben. Fragmentierung wäre also kein Problem in der LOH (Referenzen: meine eigenen Gedanken und einige empirische Beobachtungen mit VMMap, daher sind Kommentare sehr willkommen!).

Da die LOH-Segmente jedoch aus dem virtuellen Adressraum zugewiesen werden, ist auch hier die Fragmentierung ein Problem - genau wie bei nicht verwalteten Anwendungen. Tatsächlich sollte das Zuweisungsmuster für beide Anwendungen für den Speichermanager des Betriebssystems sehr ähnlich aussehen. (?) Mit einem Unterschied: Die Arrays werden gleichzeitig vom GC freigegeben. "Wirklich große Arrays" erzeugen jedoch viel Druck auf den GC. Nur eine relativ kleine Anzahl von "wirklich großen Arrays" kann gleichzeitig gehalten werden, bis die Sammlung stattfindet. Grundsätzlich verbringt die Anwendung normalerweise eine angemessene Zeit (etwa 5,45%) im GC, auch weil praktisch alle Sammlungen teure Gen2-Sammlungen sind und fast jede Zuordnung zu einer solchen Gen-2-Sammlung führt.

Hier kann Pooling erheblich helfen. Sobald die Arrays nicht an das Betriebssystem freigegeben, sondern im Pool gesammelt wurden, stehen sie sofort für weitere Anfragen zur Verfügung. sofort. (Dies ist ein Grund, warum IDisposable nicht nur für nicht verwaltete Ressourcen gedacht ist).Das Framework/die Bibliothek muss lediglich sicherstellen, dass die Arrays früh genug im Pool platziert werden und die Wiederverwendung größerer Arrays für Situationen ermöglicht wird, in denen tatsächlich eine kleinere Größe benötigt wird.

3

Zuerst ein wenig darüber nachdenken, was ein großes Objekt ist. In .net wird ein großes Objekt als 85.000 oder mehr Bytes betrachtet. Haben Sie wirklich so große Objekte oder haben Sie ein sehr großes Diagramm von kleineren Objekten?

Wenn es sich um ein Diagramm kleiner Objekte handelt, werden diese im SOH (Small Object Heap) gespeichert. Wenn Sie in diesem Fall Objekte erstellen und sofort loslassen, profitieren Sie am besten von den Optimierungen des Garbage Collectors, die ein Generationenmodell voraussetzen. Was ich meine ist, dass du entweder Objekte erstellst und sie zum Sterben gehen lässt oder sie für immer hältst. Sich an sie zu halten, "nur für eine Weile", oder mit anderen Worten, Pooling, wird sie nur zu höheren Generationen (bis Gen 2) befördert haben und das wird die Leistung des GC zunichtemachen, weil das Aufräumen von Gen 2 Objekten teuer ist (ewige Objekte in Gen 2 sind jedoch nicht teuer). Mach dir keine Sorgen über Speicherfragmentierung. Es sei denn, Sie tun Interop oder ausgefallene Dinge wie das Festhalten von Objekten, der GC ist sehr effektiv bei der Vermeidung von Speicherfragmentierung - er komprimiert den Speicher, der aus dem kurzlebigen Segment freigesetzt wird.

Wenn Sie tatsächlich sehr große Objekte haben (zum Beispiel sehr große Arrays), dann kann es sich lohnen, sie zu poolen. Beachten Sie jedoch, dass, wenn die Arrays Verweise auf kleinere Objekte enthalten, deren Zusammenführung zu den Problemen führen wird, über die ich im vorherigen Absatz gesprochen habe. Daher sollten Sie das Array (mit seinen Referenzen auf null) häufig säubern (jede Iteration?).

auch sagen, dass die Tatsache, dass Sie anrufen IDisposable nicht Objekte reinigen. Der GC tut dies. Dispose ist für die Bereinigung nicht verwalteter Ressourcen zuständig. Dennoch ist es sehr wichtig, dass Sie entsorgen, deren Klasse auf jedes Objekt behalten Aufruf implementiert IDisposable (der beste Weg ist schließlich durch), weil Sie möglicherweise nicht verwalteten Ressourcen freisetzen, werden sofort und auch, weil Sie GC sagen es braucht nicht zu anrufen der Finalizer des Objekts, der zur unnötigen Förderung des Objekts führen würde, das, wie wir gesehen haben, ein Nein ist.

Fazit, der GC ist wirklich gut in der Zuteilung und Reinigung von Sachen. Wenn Sie versuchen, ihm zu helfen, führt das normalerweise zu schlechteren Ergebnissen, es sei denn, Sie wissen wirklich, was vor sich geht.

wirklich verstehen, was ich rede:

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework

Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework 2

Large Object Heap Uncovered

+0

danke. Ich kannte diese Konzepte bereits. Es gibt nur eine Sache, der ich in Ihrem Beitrag nicht nachgehe: Ein noch besserer Weg, um sicherzustellen, dass Dispose() aufgerufen wird, besteht darin, das Objekt in einem using() - Block zu verwenden. Die fragliche Anwendung ** ist ** Zuweisung einer Menge von 'sehr großen' Arrays. Leider beantwortet dies die Frage nicht. Sie haben sich nur auf die verwaltete Seite bezogen. Wie wäre es mit der nicht verwalteten Seite zu vergleichen? – user492238