Ergänzend zu den bereits vorhandenen, hilfreichen Antworten mit Anleitungen, wann welcher Ansatz zu verwenden ist, und einem Leistungsvergleich .
DraußenVerwenden Sie einer Pipeline (PSv3 +):
$ Objekte .Name
wie in der Antwort von rageandqq gezeigt , die sowohl syntaktisch einfacher als auch syntaktisch einfacher ist viel schneller ist .
Verwenden Sie in einer Pipeline, in der das Ergebnis weiterverarbeitet werden muss oder die Ergebnisse nicht in den gesamten Speicher passen, Folgendes:
$ Objekte | Select-Object -ExpandProperty Name
- Die Notwendigkeit
-ExpandProperty
wird in der Antwort von Scott Saad erläutert .
- Sie erhalten die üblichen Pipeline-Vorteile einer Einzelverarbeitung, die in der Regel sofort eine Ausgabe erzeugt und die Speichernutzung konstant hält (es sei denn, Sie erfassen die Ergebnisse letztendlich ohnehin im Speicher).
- Kompromiss :
- Die Nutzung der Pipeline ist vergleichsweise langsam .
Bei kleinen Eingabesammlungen (Arrays) werden Sie den Unterschied wahrscheinlich nicht bemerken , und insbesondere in der Befehlszeile ist es manchmal wichtiger, den Befehl einfach eingeben zu können.
Hier ist eine einfach zu tippende Alternative , die jedoch der langsamste Ansatz ist . Es verwendet eine vereinfachte ForEach-Object
Syntax, die als Operationsanweisung bezeichnet wird (wieder PSv3 +) :; Die folgende PSv3 + -Lösung lässt sich einfach an einen vorhandenen Befehl anhängen:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
Der Vollständigkeit halber: Die wenig bekannte PSv4 + -Array- .ForEach()
Methode , die in diesem Artikel ausführlicher behandelt wird , ist eine weitere Alternative :
# By property name (string):
$objects.ForEach('Name')
# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
Dieser Ansatz ähnelt der Mitgliederaufzählung mit denselben Kompromissen, außer dass die Pipeline-Logik nicht angewendet wird. es ist geringfügig langsamer , aber immer noch merklich schneller als die Pipeline.
Zum Extrahieren eines einzelnen Eigenschaftswerts nach Namen ( Zeichenfolgenargument ) entspricht diese Lösung der Elementaufzählung (obwohl letztere syntaktisch einfacher ist).
Die Skript-Block - Variante ermöglicht beliebige Transformationen ; Es ist eine schnellere Alternative zum Pipeline-basierten ForEach-Object
Cmdlet ( %
) .
Vergleich der Leistung der verschiedenen Ansätze
Hier finden Sie Beispiel-Timings für die verschiedenen Ansätze, basierend auf einer Eingabesammlung von 10,000
Objekten , gemittelt über 10 Läufe. Die absoluten Zahlen sind nicht wichtig und variieren aufgrund vieler Faktoren. Sie sollten jedoch einen Eindruck von der relativen Leistung vermitteln (die Zeitangaben stammen von einer Single-Core-Windows 10-VM:
Wichtig
Die relative Leistung hängt davon ab, ob es sich bei den Eingabeobjekten um Instanzen regulärer .NET-Typen (z. B. als Ausgabe von Get-ChildItem
) oder um [pscustomobject]
Instanzen (z. B. als Ausgabe von Convert-FromCsv
) handelt.
Der Grund dafür ist, dass [pscustomobject]
Eigenschaften von PowerShell dynamisch verwaltet werden und schneller auf sie zugreifen können als die regulären Eigenschaften eines (statisch definierten) regulären .NET-Typs. Beide Szenarien werden im Folgenden behandelt.
Bei den Tests werden bereits im Speicher befindliche Sammlungen als Eingabe verwendet, um sich auf die reine Eigenschaftsextraktionsleistung zu konzentrieren. Mit einem Streaming-Cmdlet / Funktionsaufruf als Eingabe sind Leistungsunterschiede im Allgemeinen viel weniger ausgeprägt, da die in diesem Aufruf verbrachte Zeit den größten Teil der aufgewendeten Zeit ausmachen kann.
Der Kürze halber %
wird für das ForEach-Object
Cmdlet ein Alias verwendet .
Allgemeine Schlussfolgerungen , die sowohl für den regulären .NET-Typ als auch für die [pscustomobject]
Eingabe gelten:
Die Mitgliederaufzählung ( $collection.Name
) und die foreach ($obj in $collection)
Lösungen sind bei weitem die schnellsten , um den Faktor 10 oder mehr schneller als die schnellste Pipeline-basierte Lösung.
Überraschenderweise ist % Name
die Leistung viel schlechter als % { $_.Name }
- siehe dieses GitHub-Problem .
PowerShell Core übertrifft hier Windows Powershell durchweg.
Timings mit regulären .NET-Typen :
- PowerShell Core v7.0.0-Vorschau.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.005
1.06 foreach($o in $objects) { $o.Name } 0.005
6.25 $objects.ForEach('Name') 0.028
10.22 $objects.ForEach({ $_.Name }) 0.046
17.52 $objects | % { $_.Name } 0.079
30.97 $objects | Select-Object -ExpandProperty Name 0.140
32.76 $objects | % Name 0.148
- Windows PowerShell v5.1.18362.145
Comparing property-value extraction methods with 10000 input objects, averaged over 10 runs...
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.012
1.32 foreach($o in $objects) { $o.Name } 0.015
9.07 $objects.ForEach({ $_.Name }) 0.105
10.30 $objects.ForEach('Name') 0.119
12.70 $objects | % { $_.Name } 0.147
27.04 $objects | % Name 0.312
29.70 $objects | Select-Object -ExpandProperty Name 0.343
Schlussfolgerungen:
- In Powershell - Core ,
.ForEach('Name')
deutlich überlegen .ForEach({ $_.Name })
. In Windows PowerShell ist letzteres seltsamerweise schneller, wenn auch nur am Rande.
Timings mit [pscustomobject]
Instanzen :
- PowerShell Core v7.0.0-Vorschau.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.006
1.11 foreach($o in $objects) { $o.Name } 0.007
1.52 $objects.ForEach('Name') 0.009
6.11 $objects.ForEach({ $_.Name }) 0.038
9.47 $objects | Select-Object -ExpandProperty Name 0.058
10.29 $objects | % { $_.Name } 0.063
29.77 $objects | % Name 0.184
- Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.008
1.14 foreach($o in $objects) { $o.Name } 0.009
1.76 $objects.ForEach('Name') 0.015
10.36 $objects | Select-Object -ExpandProperty Name 0.085
11.18 $objects.ForEach({ $_.Name }) 0.092
16.79 $objects | % { $_.Name } 0.138
61.14 $objects | % Name 0.503
Schlussfolgerungen:
Beachten Sie, wie die [pscustomobject]
Eingabe die .ForEach('Name')
auf Skriptblöcken basierende Variante bei weitem übertrifft .ForEach({ $_.Name })
.
In ähnlicher Weise beschleunigt die [pscustomobject]
Eingabe die Pipeline-Basis Select-Object -ExpandProperty Name
in Windows PowerShell praktisch auf dem Niveau von .ForEach({ $_.Name })
, aber in PowerShell Core immer noch etwa 50% langsamer.
Kurz gesagt: Mit der ungeraden Ausnahme % Name
, mit [pscustomobject]
den String-basierten Methoden zur Referenzierung der Eigenschaften übertreffen die Skript-basiert ist.
Quellcode für die Tests :
Hinweis:
Download-Funktion Time-Command
von diesem Gist , um diese Tests auszuführen.
Stellen Sie $useCustomObjectInput
zu $true
mit messen , [pscustomobject]
anstatt Instanzen.
$count = 1e4 # max. input object count == 10,000
$runs = 10 # number of runs to average
# Note: Using [pscustomobject] instances rather than instances of
# regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false
# Create sample input objects.
if ($useCustomObjectInput) {
# Use [pscustomobject] instances.
$objects = 1..$count | % { [pscustomobject] @{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
# Use instances of a regular .NET type.
# Note: The actual count of files and folders in your home dir. tree
# may be less than $count
$objects = Get-ChildItem -Recurse $HOME | Select-Object -First $count
}
Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Name },
{ $objects | % Name },
{ $objects | % { $_.Name } },
{ $objects.ForEach('Name') },
{ $objects.ForEach({ $_.Name }) },
{ $objects.Name },
{ foreach($o in $objects) { $o.Name } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*
$results = @($objects | %{ $_.Name })
. Dies kann manchmal bequemer über die Befehlszeile eingegeben werden, obwohl ich denke, dass Scotts Antwort im Allgemeinen besser ist.