Was sind einige Tipps, um die Speichernutzung von .NET-Anwendungen zu reduzieren? Betrachten Sie das folgende einfache C # -Programm.
class Program
{
static void Main(string[] args)
{
Console.ReadLine();
}
}
Der Task-Manager wurde im Release- Modus für x64 kompiliert und außerhalb von Visual Studio ausgeführt. Er meldet Folgendes:
Working Set: 9364k
Private Working Set: 2500k
Commit Size: 17480k
Es ist ein bisschen besser, wenn es nur für x86 kompiliert wurde :
Working Set: 5888k
Private Working Set: 1280k
Commit Size: 7012k
Ich habe dann das folgende Programm ausprobiert, das dasselbe tut, aber versucht, die Prozessgröße nach der Laufzeitinitialisierung zu reduzieren:
class Program
{
static void Main(string[] args)
{
minimizeMemory();
Console.ReadLine();
}
private static void minimizeMemory()
{
GC.Collect(GC.MaxGeneration);
GC.WaitForPendingFinalizers();
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle,
(UIntPtr) 0xFFFFFFFF, (UIntPtr)0xFFFFFFFF);
}
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetProcessWorkingSetSize(IntPtr process,
UIntPtr minimumWorkingSetSize, UIntPtr maximumWorkingSetSize);
}
Die Ergebnisse auf x86 Release außerhalb von Visual Studio:
Working Set: 2300k
Private Working Set: 964k
Commit Size: 8408k
Was ein bisschen besser ist, aber für ein so einfaches Programm immer noch übertrieben erscheint. Gibt es irgendwelche Tricks, um einen C # -Prozess etwas schlanker zu machen? Ich schreibe ein Programm, das die meiste Zeit im Hintergrund ausgeführt wird. Ich mache bereits Benutzeroberflächenmaterial in einer separaten Anwendungsdomäne, was bedeutet, dass das Benutzeroberflächenmaterial sicher entladen werden kann, aber 10 MB zu beanspruchen, wenn es nur im Hintergrund sitzt, scheint übermäßig.
PS Warum es mich interessieren würde --- (Power-) Benutzer neigen dazu, sich über diese Dinge Sorgen zu machen. Auch wenn es fast keine Auswirkungen auf die Leistung hat, neigen semi-technisch versierte Benutzer (meine Zielgruppe) dazu, über die Speichernutzung im Hintergrundanwendungen zu zischen. Sogar ich flippe aus, wenn ich sehe, dass Adobe Updater 11 MB Speicher benötigt, und fühle mich beruhigt von der beruhigenden Note von Foobar2000, die selbst beim Spielen weniger als 6 MB benötigen kann. Ich weiß, dass dieses Zeug in modernen Betriebssystemen technisch gesehen nicht so wichtig ist, aber das bedeutet nicht, dass es keinen Einfluss auf die Wahrnehmung hat.
quelle
Antworten:
Ich werde nicht sagen, dass Sie den Speicherbedarf Ihrer Anwendung ignorieren sollten - offensichtlich sind kleinere und effizientere eher wünschenswert. Sie sollten jedoch Ihre tatsächlichen Bedürfnisse berücksichtigen.
Wenn Sie eine Standardanwendung für Windows Forms und WPF-Clients schreiben, die auf dem PC einer Person ausgeführt werden soll und wahrscheinlich die Hauptanwendung ist, in der der Benutzer arbeitet, können Sie die Speicherzuweisung weniger genau kennen. (Solange alles freigegeben wird.)
Um jedoch einige Leute anzusprechen, die sagen, dass sie sich keine Sorgen machen sollen: Wenn Sie eine Windows Forms-Anwendung schreiben, die in einer Terminaldienstumgebung auf einem gemeinsam genutzten Server ausgeführt wird, der möglicherweise von 10, 20 oder mehr Benutzern verwendet wird, dann ja müssen Sie unbedingt die Speichernutzung berücksichtigen. Und Sie müssen wachsam sein. Der beste Weg, dies zu beheben, ist ein gutes Datenstrukturdesign und das Befolgen von Best Practices hinsichtlich des Zeitpunkts und der Zuweisung.
quelle
.NET-Anwendungen haben im Vergleich zu nativen Anwendungen einen größeren Platzbedarf, da beide die Laufzeit und die Anwendung in den Prozess laden müssen. Wenn Sie etwas wirklich Ordentliches wollen, ist .NET möglicherweise nicht die beste Option.
Beachten Sie jedoch, dass die erforderlichen Speicherseiten, wenn Ihre Anwendung hauptsächlich im Ruhezustand ist, nicht mehr über genügend Arbeitsspeicher verfügen und daher das System die meiste Zeit nicht wirklich stark belasten.
Wenn Sie den Platzbedarf klein halten möchten, müssen Sie über die Speichernutzung nachdenken. Hier einige Ideen:
List<T>
ähnliche Typen, die bei Bedarf die Kapazität verdoppeln, da sie zu bis zu 50% Abfall führen können.Das ist wahrscheinlich keine vollständige Liste, sondern nur ein paar Ideen.
quelle
Eine Sache, die Sie in diesem Fall berücksichtigen müssen, sind die Speicherkosten der CLR. Die CLR wird für jeden .NET-Prozess geladen und berücksichtigt daher die Speicherüberlegungen. Für solch ein einfaches / kleines Programm werden die Kosten der CLR Ihren Speicherbedarf dominieren.
Es wäre viel lehrreicher, eine echte Anwendung zu erstellen und deren Kosten im Vergleich zu den Kosten dieses Basisprogramms anzuzeigen.
quelle
Keine konkreten Vorschläge an sich, aber Sie können sich den CLR Profiler ansehen (kostenloser Download von Microsoft).
Schauen Sie sich nach der Installation diese Anleitung an .
Von der Anleitung:
quelle
Vielleicht möchten Sie sich die Speichernutzung einer "echten" Anwendung ansehen.
Ähnlich wie bei Java gibt es unabhängig von der Programmgröße einen festen Overhead für die Laufzeit, aber der Speicherverbrauch ist nach diesem Zeitpunkt viel vernünftiger.
quelle
Es gibt immer noch Möglichkeiten, die private Arbeitsmenge dieses einfachen Programms zu reduzieren:
NGEN Sie Ihre Bewerbung. Dadurch werden die JIT-Kompilierungskosten aus Ihrem Prozess entfernt.
Trainieren Sie Ihre Anwendung mit MPGO und reduzieren Sie die Speichernutzung .
quelle
Es gibt viele Möglichkeiten, Ihren Fußabdruck zu verringern.
Eine Sache, mit der Sie in .NET immer leben müssen, ist, dass das native Image Ihres IL-Codes sehr groß ist
Und dieser Code kann nicht vollständig zwischen Anwendungsinstanzen geteilt werden. Selbst NGEN- Baugruppen sind nicht vollständig statisch, sie haben noch einige kleine Teile, die JITting benötigen.
Menschen neigen auch dazu, Code zu schreiben, der den Speicher viel länger als nötig blockiert.
Ein häufig gesehenes Beispiel: Nehmen Sie einen Datenleser und laden Sie den Inhalt in eine DataTable, um ihn in eine XML-Datei zu schreiben. Sie können leicht auf eine OutOfMemoryException stoßen. OTOH, Sie könnten einen XmlTextWriter verwenden und durch den Datenleser scrollen und XmlNodes ausgeben, während Sie durch den Datenbankcursor scrollen. Auf diese Weise haben Sie nur den aktuellen Datenbankeintrag und seine XML-Ausgabe im Speicher. Was niemals (oder unwahrscheinlich) eine höhere Garbage Collection-Generation erhalten wird und somit wiederverwendet werden kann.
Das Gleiche gilt für das Abrufen einer Liste einiger Instanzen, das Ausführen einiger Aufgaben (die Tausende neuer Instanzen hervorbringen, auf die möglicherweise irgendwo verwiesen wird), und obwohl Sie sie später nicht benötigen, verweisen Sie bis nach dem Foreach auf alles. Wenn Sie Ihre Eingabeliste und Ihre temporären Nebenprodukte explizit auf Null setzen, kann dieser Speicher bereits vor dem Verlassen Ihrer Schleife wiederverwendet werden.
C # hat eine hervorragende Funktion namens Iteratoren. Sie ermöglichen das Streamen von Objekten durch Scrollen durch Ihre Eingabe und behalten nur die aktuelle Instanz bei, bis Sie die nächste erhalten. Selbst wenn Sie LINQ verwenden, müssen Sie nicht alles behalten, nur weil Sie möchten, dass es gefiltert wird.
quelle
Adressierung der allgemeinen Frage im Titel und nicht der spezifischen Frage:
Wenn Sie eine COM-Komponente verwenden, die viele Daten zurückgibt (z. B. große 2xN-Arrays von Doubles) und nur einen kleinen Bruchteil benötigt, können Sie eine Wrapper-COM-Komponente schreiben, die den Speicher vor .NET verbirgt und nur die Daten zurückgibt, die vorhanden sind erforderlich.
Das habe ich in meiner Hauptanwendung getan und den Speicherverbrauch erheblich verbessert.
quelle
Ich habe festgestellt, dass die Verwendung der SetProcessWorkingSetSize- oder EmptyWorkingSet-APIs, um Speicherseiten in einem lang laufenden Prozess regelmäßig auf die Festplatte zu zwingen, dazu führen kann, dass der gesamte verfügbare physische Speicher auf einem Computer effektiv verschwindet, bis der Computer neu gestartet wird. Wir hatten eine .NET-DLL in einen nativen Prozess geladen, der die EmptyWorkingSet-API (eine Alternative zur Verwendung von SetProcessWorkingSetSize) verwendet, um den Arbeitssatz nach einer speicherintensiven Aufgabe zu reduzieren. Ich stellte fest, dass ein Computer nach einem Tag bis zu einer Woche eine 99% ige physische Speichernutzung im Task-Manager anzeigt, während keine Prozesse eine signifikante Speichernutzung aufweisen. Bald darauf reagierte der Computer nicht mehr und erforderte einen harten Neustart. Bei diesen Computern handelte es sich um mehr als 2 Dutzend Windows Server 2008 R2- und 2012 R2-Server, die sowohl auf physischer als auch auf virtueller Hardware ausgeführt wurden.
Möglicherweise hatte das Laden des .NET-Codes in einen nativen Prozess etwas damit zu tun, aber die Verwendung von EmptyWorkingSet (oder SetProcessWorkingSetSize) erfolgt auf eigenes Risiko. Verwenden Sie es möglicherweise nur einmal nach dem ersten Start Ihrer Anwendung. Ich habe beschlossen, den Code zu deaktivieren und den Garbage Collector die Speichernutzung selbst verwalten zu lassen.
quelle