wake-up-neo.com

Reduzierung der Speichernutzung von .NET-Anwendungen?

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();
    }
}

Kompiliert im Release Modus für x64 und ausgeführt außerhalb von Visual Studio die Aufgabe Manager 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 das Gleiche 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

Welches ist ein bisschen besser, aber es scheint immer noch zu viel für ein so einfaches Programm. Gibt es irgendwelche Tricks, um einen C # -Prozess ein bisschen schlanker zu machen? Ich schreibe ein Programm, das die meiste Zeit im Hintergrund laufen soll. Ich führe bereits Benutzerschnittstelleninhalte in einem separaten Anwendungsdomäne aus, was bedeutet, dass die Benutzerschnittstelleninhalte sicher entladen werden können, aber es scheint übermäßig, 10 MB zu beanspruchen, wenn sie sich nur im Hintergrund befinden.

P.S Warum interessiert es mich --- (Power-) Benutzer, neigen dazu, sich über diese Dinge Gedanken zu machen. Auch wenn dies fast keine Auswirkungen auf die Leistung hat, neigen semi-technisch versierte Benutzer (meine Zielgruppe) dazu, hissy Fits über die Speichernutzung von Hintergrundanwendungen zu machen. Selbst wenn ich sehe, dass Adobe Updater 11 MB Speicherplatz einnimmt, kann ich mich von der beruhigenden Wirkung von Foobar2000 beruhigt fühlen, die selbst beim Abspielen unter 6 MB liegen kann. Ich weiß, dass dieses Zeug in modernen Betriebssystemen technisch nicht so wichtig ist, aber das bedeutet nicht, dass es keinen Einfluss auf die Wahrnehmung hat.

106
Robert Fraser
  1. Vielleicht möchten Sie die Frage zum Stapelüberlauf . NET EXE-Speicherbedarf auschecken.
  2. Der MSDN-Blogbeitrag Arbeitssatz! = Tatsächlicher Speicherbedarf befasst sich mit der Entmystifizierung des Arbeitssatzes, der Verarbeitung des Speichers und der Durchführung Genaue Berechnungen des gesamten In-RAM-Verbrauchs.

Ich werde nicht sagen, dass Sie den Speicherbedarf Ihrer Anwendung ignorieren sollten - offensichtlich sind kleinere und effizientere Anwendungen wünschenswert. Sie sollten jedoch berücksichtigen, was Ihre tatsächlichen Bedürfnisse sind.

Wenn Sie standardmäßige Windows Forms- und WPF-Clientanwendungen schreiben, die auf dem PC einer Person ausgeführt werden sollen und wahrscheinlich die Hauptanwendung sind, in der der Benutzer tätig ist, kann es sein, dass Sie hinsichtlich der Speicherzuweisung weniger sachkundig sind. (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 genutzt wird, dann ja müssen Sie unbedingt die Speichernutzung berücksichtigen. Und Sie müssen wachsam sein. Dies lässt sich am besten durch ein gutes Datenstrukturdesign und durch Befolgen der bewährten Methoden zum Zeitpunkt und zu den zugewiesenen Daten beheben.

32
John Rudy

.NET-Anwendungen haben im Vergleich zu nativen Anwendungen eine größere Stellfläche, da sowohl die Laufzeit als auch die Anwendung geladen werden müssen. Wenn Sie etwas wirklich Ordentliches wollen, ist .NET möglicherweise nicht die beste Option.

Beachten Sie jedoch, dass die erforderlichen Speicherseiten ausgelagert werden, wenn die Anwendung zum größten Teil in den Ruhezustand versetzt wird, und daher das System in den meisten Fällen nicht sonderlich belasten.

Wenn Sie den Footprint klein halten möchten, müssen Sie über die Speichernutzung nachdenken. Hier sind ein paar Ideen:

  • Reduzieren Sie die Anzahl der Objekte und stellen Sie sicher, dass Sie sich nicht länger als erforderlich an einer Instanz festhalten.
  • Sei dir bewusst, dass List<T> und ähnliche Typen, die bei Bedarf die Kapazität verdoppeln, da sie zu bis zu 50% Abfall führen können.
  • Sie können Werttypen anstelle von Referenztypen verwenden, um mehr Speicher auf dem Stapel zu erzwingen. Beachten Sie jedoch, dass der Standard-Stapelspeicher nur 1 MB beträgt.
  • Vermeiden Sie Objekte mit mehr als 85000 Bytes, da diese zu LOH gelangen, das nicht komprimiert ist und daher leicht fragmentiert werden kann.

Das ist wahrscheinlich keine vollständige Liste, sondern nur ein paar Ideen.

44
Brian Rasmussen

In diesem Fall müssen Sie die Speicherkosten der CLR berücksichtigen. Die CLR wird für jeden .Net-Prozess geladen und berücksichtigt daher die Speichererwägungen. Für solch ein einfaches/kleines Programm werden die Kosten der CLR Ihren Speicherbedarf dominieren.

Es wäre viel aufschlussreicher, eine echte Anwendung zu erstellen und deren Kosten im Vergleich zu den Kosten dieses Basisprogramms zu ermitteln.

16
JaredPar

Keine konkreten Vorschläge an sich, aber Sie können sich den CLR Profiler (kostenloser Download von Microsoft) ansehen.
Wenn Sie es installiert haben, schauen Sie sich diese How-to-Seite an.

Vom How-to:

In dieser Anleitung wird gezeigt, wie Sie das CLR-Profiler-Tool verwenden, um das Speicherzuordnungsprofil Ihrer Anwendung zu untersuchen. Mit CLR Profiler können Sie Code identifizieren, der Speicherprobleme verursacht, z. B. Speicherlecks und übermäßige oder ineffiziente Garbage Collection.

7
Donut

Vielleicht möchten Sie die Speichernutzung einer "echten" Anwendung untersuchen.

Ähnlich wie bei Java gibt es einen festen Overhead für die Laufzeit, unabhängig von der Programmgröße, aber der Speicherverbrauch ist nach diesem Zeitpunkt viel vernünftiger.

6
Eric Petroelje

Es gibt immer noch Möglichkeiten, den privaten Arbeitsumfang dieses einfachen Programms zu reduzieren:

  1. NGEN Ihre Bewerbung. Dadurch entfallen die Kosten für die JIT-Kompilierung für Ihren Prozess.

  2. Trainieren Sie Ihre Anwendung mit MPGO Reduzierung des Speicherbedarfs und dann mit NGEN.

4
Feng Yuan

Es gibt viele Möglichkeiten, Ihren Fußabdruck zu reduzieren.

Eine Sache, mit der Sie in . NET immer leben müssen, ist, dass die Größe des nativen Abbilds Ihres IL-Codes sehr groß ist

Und dieser Code kann nicht vollständig zwischen Anwendungsinstanzen geteilt werden. Auch NGEN'ed Baugruppen sind nicht vollständig statisch, sie haben noch einige kleine Teile, die JITting benötigen.

Leute neigen auch dazu, Code zu schreiben, der den Speicher viel länger blockiert als nötig.

Ein häufig gesehenes Beispiel: Nehmen Sie einen Datenleser, und laden Sie den Inhalt in eine Datentabelle, um ihn in eine XML-Datei zu schreiben. Sie können problemlos eine OutOfMemoryException ausführen. OTOH, Sie können einen XmlTextWriter verwenden und durch den Datenbereich scrollen, wobei XmlNodes ausgegeben werden, wenn Sie durch den Datenbankcursor scrollen. Auf diese Weise haben Sie nur den aktuellen Datenbankeintrag und dessen XML-Ausgabe im Speicher. Was niemals (oder unwahrscheinlich) zu einer höheren Speicherbereinigungsgeneration führen wird und somit wiederverwendet werden kann.

Das Gleiche gilt für das Abrufen einer Liste einiger Instanzen, für das Ausführen einiger Dinge (die Tausende neuer Instanzen hervorbringen, auf die möglicherweise irgendwo verwiesen wird), und auch wenn Sie sie später nicht benötigen, verweisen Sie immer noch auf alles, bis nach foreach. Wenn Sie Ihre Eingabeliste und Ihre temporären Nebenprodukte explizit auf Null setzen, kann dieser Speicher wiederverwendet werden, noch bevor Sie die Schleife verlassen.

C # hat eine hervorragende Funktion namens Iteratoren. Sie ermöglichen es Ihnen, Objekte durch Scrollen durch Ihre Eingabe zu streamen und die aktuelle Instanz nur so lange zu behalten, bis Sie die nächste erhalten. Selbst wenn Sie LINQ verwenden, müssen Sie nicht alles behalten, nur weil Sie wollten, dass es gefiltert wird.

2
Robert Giesecke

Ansprechen 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 mit Doppelwerten) und nur ein kleiner Bruchteil benötigt wird, können Sie eine Wrapper-COM-Komponente schreiben, die den Speicher von .NET verbirgt und nur die Daten zurückgibt, die vorhanden sind erforderlich.

Das habe ich in meiner Hauptanwendung getan und den Speicherverbrauch erheblich verbessert.

1
Peter Mortensen

Ich habe festgestellt, dass die Verwendung der SetProcessWorkingSetSize- oder EmptyWorkingSet-APIs zum Erzwingen, dass Speicherseiten in regelmäßigen Abständen während eines langen Prozesses auf die Festplatte verschoben werden, dazu führen kann, dass der gesamte verfügbare physische Speicher auf einem Computer effektiv verschwindet, bis der Computer neu gestartet wird. Wir hatten ein .NET DLL) in einen nativen Prozess geladen, der die EmptyWorkingSet-API (eine Alternative zur Verwendung von SetProcessWorkingSetSize) verwendete, um den Arbeitssatz nach Ausführung einer speicherintensiven Aufgabe zu reduzieren Zwischen einem Tag und einer Woche zeigte ein Computer im Task-Manager eine Speichernutzung von 99%, während keine Prozesse eine signifikante Speichernutzung aufwiesen. Kurz nachdem der Computer nicht mehr reagierte und einen Neustart erforderte. Diese Maschinen waren über 2 Dutzend Windows Server 2008 R2- und 2012 R2-Server, die sowohl auf physischer als auch auf virtueller Hardware ausgeführt werden.

Vielleicht hatte das Laden des .NET-Codes in einen systemeigenen 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.

0
Stuart Welch