Keine Speicherbereinigung während der Ausführung der PowerShell-Pipeline

8

UPDATE: Der folgende Fehler scheint mit PowerShell 5 behoben zu sein. Der Fehler bleibt in 3 und 4. Bearbeiten Sie also keine riesigen Dateien mit der Pipeline, es sei denn, Sie führen PowerShell 2 oder 5.

Betrachten Sie das folgende Code-Snippet:

%Vor%

Dies führt dazu, dass die Speicherbelegung von PowerShell unkontrolliert zunimmt. Nachdem ich Get-DummyData | Out-Null einige Male ausgeführt habe, habe ich gesehen, dass die Speicherauslastung von PowerShell bis zu 4 GB beträgt.

Nach ANTS Memory Profiler haben wir eine Menge Dinge, die in der Finalisierungswarteschlange des Garbage Collectors herumstehen. Wenn ich [GC]::Collect() aufrufe, geht der Speicher von 4 GB auf nur 70 MB. Also haben wir genau genommen kein Speicherleck.

Nun, es ist nicht gut genug für mich, [GC]::Collect() aufrufen zu können, wenn ich mit einer langlebigen Pipelineoperation fertig bin. Ich brauche eine Garbage-Collection, um während einer Pipelineoperation zu passieren. Wenn ich jedoch versuche, [GC]::Collect() aufzurufen, während die Pipeline ausgeführt wird ...

%Vor%

... das Problem bleibt bestehen. Die Speicherauslastung wächst wieder unkontrolliert. Ich habe versucht, verschiedene Variationen davon, wie zum Beispiel [GC]::WaitForPendingFinalizers() , Start-Sleep -Seconds 10 , etc. Ich habe versucht, Garbage Collector zu ändern Latenzmodi und Erzwingen der Verwendung von PowerShell für Server Garbage Collection ohne Erfolg. Ich kann den Garbage Collector einfach nicht dazu bringen, seine Sache zu erledigen, während die Pipeline ausgeführt wird.

Dies ist in PowerShell 2.0 kein Problem. Interessant ist auch, dass $null = Get-DummyData auch ohne Speicherprobleme funktioniert. Es scheint also eher an die Pipeline gebunden zu sein als an die Tatsache, dass wir Tonnen von Strings erzeugen.

Wie kann ich verhindern, dass mein Gedächtnis während langer Pipelines unkontrolliert wächst?

Seitliche Anmerkung:

Meine Get-DummyData-Funktion dient nur zu Demonstrationszwecken. Mein Problem ist, dass ich große Dateien in PowerShell nicht mit Get-Content oder Import-Csv lesen kann. Nein, ich nicht speichere den Inhalt dieser Dateien in Variablen. Ich verwende die Pipelines streng , wie ich es mir vorstelle zu. Get-Content .\super-huge-file.txt | Out-Null erzeugt das gleiche Problem.

    
Phil 24.07.2015, 22:31
quelle

2 Antworten

7

Ein paar Dinge, auf die Sie hier hinweisen sollten. Erstens funktionieren GC-Aufrufe in der Pipeline. Hier ist ein Pipelineskript, das nur den GC aufruft:

%Vor%

Hier ist das Perfmon-Diagramm von GCs während der Skript-Laufzeit:

Aber nur weil Sie den GC aufrufen, bedeutet das nicht, dass die private Speicherbelegung auf den Wert zurückkehrt, den Sie vor dem Start Ihres Skripts hatten. Ein GC-Collect sammelt nur Speicher, der nicht mehr verwendet wird. Wenn eine verwurzelte Referenz auf ein Objekt vorhanden ist, kann sie nicht gesammelt (freigegeben) werden. Während GC-Systeme in der Regel nicht im C / C ++ - Sinne auslaufen, können sie Speicherbehältnisse aufweisen, die Objekte länger halten als sie sollten.

Wenn man dies mit einem Speicherprofiler anschaut, scheint der Großteil des überschüssigen Speichers von einer Kopie der Zeichenkette mit der Parameterbindungsinformation belegt zu sein:

Die Wurzel für diese Zeichenfolgen sieht folgendermaßen aus:

Ich frage mich, ob es eine Protokollierungsfunktion gibt, die dazu führt, dass PowerShell an einem stringgebundenen Formular in der Pipeline hängen bleibt?

BTW In diesem speziellen Fall ist es viel effizienter, $ 0 zuzuweisen, um die Ausgabe zu ignorieren:

%Vor%

Wenn Sie eine Datei auch einfach bearbeiten müssen, lesen Sie den Befehl Edit-File in den PowerShell-Community-Erweiterungen 3.2.0. Es sollte speichereffizient sein, solange Sie nicht den Switch-Parameter SingleString verwenden.

    
Keith Hill 25.07.2015, 21:03
quelle
1

Es ist nicht ungewöhnlich, dass die nativen Cmdlets nicht perfekt funktionieren, wenn Sie etwas Ungewöhnliches wie die Verarbeitung einer umfangreichen Textdatei tun. Persönlich habe ich festgestellt, dass das Arbeiten mit großen Dateien in Powershell viel besser ist, wenn Sie es mit System.IO.StreamReader scripten:

%Vor%

Beachten Sie, dass Sie den absoluten Pfad in der ArgumentList verwenden sollten. Für mich scheint es immer anzunehmen, dass Sie sich in Ihrem Home-Verzeichnis mit relativen Pfaden befinden.

Get-Content soll einfach das gesamte Objekt als Array in den Speicher schreiben und dann ausgeben. Ich denke, es ruft nur System.IO.File.ReadAllLines ().

Ich habe keine Möglichkeit, Powershell mitzuteilen, dass Elemente unmittelbar nach der Fertigstellung aus der Pipeline gelöscht werden sollen oder dass eine Funktion Elemente asynchron zurückgeben kann. Stattdessen wird die Reihenfolge beibehalten. Es kann es nicht zulassen, da es keine natürliche Möglichkeit gibt zu sagen, dass das Objekt später nicht mehr verwendet wird oder dass spätere Objekte nicht auf frühere Objekte verweisen müssen.

Die andere nette Sache über Powershell ist, dass Sie oft übernehmen auch die C # -Antworten . Ich habe File.ReadLines noch nie ausprobiert, aber das sieht so aus es könnte auch ziemlich einfach zu bedienen sein.

    
Bacon Bits 24.07.2015 23:22
quelle