PowerShell Variablen Speicherbedarf
Ich schaue ja immer mal gerne etwas hinter die Kulissen und möchte z.B. den Speicherbedarf von Variablen abschätzen. Kann ich von einem Dateiserver alle Dateien und Verzeichnisse mal eben mit Ger-ChildItem in eine Variable laden oder sollte ich besser schon während des Einlesens die Daten verarbeiten oder sogar eine richtige Datenbank bemühen. Hier mein unvollständiger Versuch einer Analyse.
Größe ermitteln?
Leider kann man in Powershell nicht einfach den Speicherbedarf einer Variablen direkt ermitteln. Als Muster habe ich alle ca. 244537 Dateien in C:\Windows eingelesen:
| Code | Eignung | Ausgabe |
|---|---|---|
SizeOf-ProperyFür einfache Variablen gibt es eine passende Funktion im -NET-Framework [System.Runtime.InteropServices.Marshal]::SizeOf([int]) [System.Runtime.InteropServices.Marshal]::SizeOf([string]) Wenn ich Copilot und anderen KIs glaube, sollte es gehen. Bei mir kommt aber immer nur ein Fehler: "MethodInvocationException: Exception calling "SizeOf" with "1" argument(s): "Type 'System.RuntimeType' cannot be marshaled as an unmanaged structure; no meaningful size or offset can be computed.""
Interessanterweise hat mit Copilot sogar einen Code geschrieben, der auch komplexe Elemente abarbeitet.
Das sieht erst einmal professionell aus aber die Ergebnisse sind komplett daneben. Ein Array mit 10.000 unterschiedlichen Strings wurde mit 548 Byte angegeben. |
Nein |
Keine |
Workingset des ProzessSpeicher des Prozess vor und nach der Belegung der Variable ermitteln. $vorher= (Get-Process -Id $pid).WorkingSet $var = Get-ChildItem C:\Windows -recurse Write-Host "WorkingSet Zunahme $((Get-Process -Id $pid).WorkingSet - $vorher)" Der Wert erscheint mir etwas hoch. Und wenn eine Variable wieder frei wird, muss eigentlich noch der "Garbage Collector" laufen |
Sehr grob |
710.496.256 |
Abfrage des GCAber das kann man ja steuern, indem ich erst einen Cleanup starte, dann die aktuelle Belegung merke und nach der Zuweisung der Variable wieder abfrage. [GC]::Collect() $vorher = [GC]::GetTotalMemory($true) $var = Get-ChildItem C:\Windows -recurse [GC]::Collect() Write-Host "Bytes used: $([GC]::GetTotalMemory($true) - $vorher)" Das wären dann ca. 22 Bytes pro Verzeichniseintrag. Das kann ich kaum glauben, denn die Summe aller Pfade durch die Anzahl ergibt schon eine durchschnittliche Pfadlänge von 117 Zeichen. Laut KI soll das aber der beste und genaueste Weg sein. Vielleicht komprimiert .NET ja die Inhalte, um Platz zu sparen |
zu nierdig |
5.578.448 |
Variable in Byte konvertierenVariable als Bytes ausgeben und aufaddieren. $var = Get-ChildItem C:\Windows -recurse $size = [System.Text.Encoding]::Unicode.GetBytes($var) Write-Host "Stringauflistung $($size)" Allerdings funktioniert dies nicht bei allen Typen. Eine Hashtable mit 10.000 Einträge liefert nur 56 Bytes. |
Nur wenige Basistypen |
594.06.942 |
Größe über Stream ermittelnVariable in einen Stream umschreiben und Speichergröße berechnen (in Byte) Der Code
ist nicht mehr nutzbar, das
Serialisierung/Deserialisierung
mit Net 9.0 entfernt wurde.
Siehe auch $var = Get-ChildItem C:\Windows -recurse $stream = New-Object System.IO.MemoryStream $formatter = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter $formatter.Serialize($stream, $var) $byteSize = $stream.Length $stream.Close() |
Nicht mehr verfügbar (Sicherheitsrisiken) |
- |
Auf der einen Seite kann ich nicht glauben, dass ein einfacher "Dir /s" über das Windows Systemverzeichnis mit ca. 250.000 Verzeichnissen und Dateien fast 600 MB RAM benötigt. Das über 2,5kByte/Objekt.
Eine Information in verschiedenen Variablen
Achtung:
Wenn Sie eine Variable immer weiter "erweitern",
dann kann das je nach Variable sehr ineffektiv sein, weil
die PowerShell manchmal die Daten erst in einen größeren
Bereich kopiert um diese dann zu erweitern.
Exemplarisch habe ich 10.000 mal einen String mit 10 Zeichen kopiert. ich hätte im besten Fall etwas mit 100.000 Bytes, Bei Unicode wegen mir auch 200.000 Bytes erwartet
| Code | Status | Bytes |
|---|---|---|
Hashtable$var=@{}
0..9999 | %{$var += "1234567890"}
$var
|
|
56 Es kommt zwar ein numerischer Wert heraus, aber der passtdefinitiv nicht. |
dynamisches Array$var=@()
0..9999 | %{$var+=("1234567890$_")}
[System.Text.Encoding]::Unicode.GetBytes($var).count
|
|
Leer: 0 Gefüllt: 297786 Das könnte passen |
Statische Array$var = [string[]]::new(10000)
0..9999 | %{$var[$_]="1234567890$_"}
|
|
Leer: 19998 Gefüllt: 297778 Das könnte passen |
Stack$DirectoryStack = [System.Collections.Generic.Stack[string]]::new()
$DirectoryStack.push("1234567890$_"}
|
|
Leer: "nichts" Gefüllt: "GetBytes" funktioniert nicht Ein Fehler verhindert die Ermittlung einer Größe |
Genau genommen habe ich auch so keinen zuverlässigen Indikator, um die Größe einer Variable im Speicher zu ermitteln. Und hier habe hier noch nicht einmal die Inhalte unterschieden.
String oder Objekt?
Das mache ich einmal für das Beispiel mit den Verzeichnissen. Wen ich mit Get-Item/Get-Childitem ein Verzeichnis bekomme, dann kann ich das einfach in ein Array ablegen. Dann habe ich aber das komplette Objekt. Wenn ich aber nur den Verzeichnisnamen als Pfad für eine spätere Weiterverarbeitung benötige, dann sollte ich vielleicht auch nur diese Information als String speichern. Gleiche Überlegungen können Sie mit "Get-ADUser" anstellen. Wollen und müssen Sie das komplette LDAP-Objekt speichern oder reicht eine Teilmenge oder einfach nur der Distinguishedname?
| Code | Eignung | Ergebnis |
|---|---|---|
String in ArrayZuerst habe ich ein Array definiert und den Fullname aller 250.000 Verzeichniseinträge gespeichert $var=@()
get-childitem c:\windows -Recurse | foreach {$var+= $_.fullname}
$var.count
[System.Text.Encoding]::Unicode.GetBytes($var).count
|
Für einfache Typen |
59.407.074 59 Megabyte könnte hinkommen. |
Verzeichnisobject in Array$var=@()
get-childitem c:\windows -Recurse | foreach {$var+= $_}
[System.Text.Encoding]::Unicode.GetBytes($var).count
|
Nicht plausibel |
59.407.074 Das kann nun aber wieder nicht stimmen. Warum sollte das Objekt mit viel mehr Informationen genauso viel Platz verbrauchen wie ein String. |
Ich wollte eigentlich damit zeigen, dass die Ablage eines "Strings" in einer Variable viel Platzsparender im Vergleich zum kompletten Objekt ist. Genau das kann ich mit "GetBytes()" nicht zeigen.
Zwischenstand
Damit bin ich nach all den Checks und Tests nicht wirklich schlauer. Zuerst hatte ich gedacht, dass ein "GetBytes()" mit eine halbwegs passende Schätzung liefert aber die Methode gibt es nicht bei allen Klassen und bei einem einfachen Vergleich der Speicherung eines Objects oder Strings zeigt, dass die Methode nicht passt. Sicher könnte ich die Variable nun mit "ExportCliXML" in eine XML-Struktur konvertieren und damit die Größe abschätzen aber das ist nur ein vager Hinweis, wie groß der Platzbedarf im Hauptspeicher ist. Gerade eine Hashtable ist hier ja ein gutes Beispiel, da neben dem Key und dem Value auch ein Hash zum Key zusätzlich gespeichert wird, um schnell zugreifen zu können.
Auf die Frage der Geschwindigkeit bin ich noch gar nicht eingegangen. Dauert das Einspeichern eines String in einem Array länger oder Kürzer als die Ablage auf einem Stack oder in einer Hashtabelle?
Ich habe an der Stelle nun erst einmal abgebrochen. Ich habe
keinen zuverlässigen Weg gefunden, den Speicherbedarf
zuverlässig abzufragen.
Vielleicht ist es eine Lösung im Skript selbst zu prüfen,
wie stark man das System belastet und notfalls abzubrechen.
Oder sie lassen es darauf ankommen, wie Windows Speicher ins
Pagefile verschiebt.
Wenn Sie eine bessere Version kennen, dann lassen Sie mich
das gerne wissen.

















