Überlegungen zur Leistung von PowerShell-Skripts
PowerShell-Skripts, die .NET direkt nutzen und die Pipeline vermeiden, sind tendenziell schneller als idiomatische PowerShell. Idiomatische PowerShell verwendet Cmdlets und PowerShell-Funktionen, die häufig die Pipeline nutzen und nur bei Bedarf auf .NET zurückgreifen.
Anmerkung
Viele der hier beschriebenen Techniken sind keine idiomatische PowerShell und können die Lesbarkeit eines PowerShell-Skripts verringern. Skriptautoren werden empfohlen, idiomatische PowerShell zu verwenden, es sei denn, die Leistung diktiert andernfalls.
Ausgabe unterdrücken
Es gibt viele Möglichkeiten, das Schreiben von Objekten in die Pipeline zu vermeiden.
- Zuordnung oder Dateiumleitung zu
$null
- Umwandlung in
[void]
- Rohr an
Out-Null
Die Geschwindigkeiten der Zuordnung zu $null
, Umwandlung zu [void]
und Dateiumleitung zu $null
sind nahezu identisch. Das Aufrufen von Out-Null
in einer großen Schleife kann jedoch erheblich langsamer sein, insbesondere in PowerShell 5.1.
$tests = @{
'Assign to $null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$null = $arraylist.Add($i)
}
}
'Cast to [void]' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
[void] $arraylist.Add($i)
}
}
'Redirect to $null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$arraylist.Add($i) > $null
}
}
'Pipe to Out-Null' = {
$arrayList = [System.Collections.ArrayList]::new()
foreach ($i in 0..$args[0]) {
$arraylist.Add($i) | Out-Null
}
}
}
10kb, 50kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value $_ }).TotalMilliseconds
[pscustomobject]@{
Iterations = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Diese Tests wurden auf einem Windows 11-Computer in PowerShell 7.3.4 ausgeführt. Die Ergebnisse werden unten angezeigt:
Iterations Test TotalMilliseconds RelativeSpeed
---------- ---- ----------------- -------------
10240 Assign to $null 36.74 1x
10240 Redirect to $null 55.84 1.52x
10240 Cast to [void] 62.96 1.71x
10240 Pipe to Out-Null 81.65 2.22x
51200 Assign to $null 193.92 1x
51200 Cast to [void] 200.77 1.04x
51200 Redirect to $null 219.69 1.13x
51200 Pipe to Out-Null 329.62 1.7x
102400 Redirect to $null 386.08 1x
102400 Assign to $null 392.13 1.02x
102400 Cast to [void] 405.24 1.05x
102400 Pipe to Out-Null 572.94 1.48x
Die Zeiten und relativen Geschwindigkeiten können je nach Hardware, Version von PowerShell und der aktuellen Workload auf dem System variieren.
Arrayzugabe
Das Generieren einer Liste von Elementen erfolgt häufig mithilfe eines Arrays mit dem Additionsoperator:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Die Arrayzugabe ist ineffizient, da Arrays eine feste Größe aufweisen. Jede Ergänzung des Arrays erstellt ein neues Array, das groß genug ist, um alle Elemente der linken und rechten Operanden zu enthalten. Die Elemente beider Operanden werden in das neue Array kopiert. Bei kleinen Sammlungen spielt dieser Aufwand möglicherweise keine Rolle. Die Leistung kann für große Sammlungen leiden.
Es gibt eine Reihe von Alternativen. Wenn Sie kein Array benötigen, sollten Sie stattdessen eine typierte generische Liste verwenden ([List<T>]
):
$results = [System.Collections.Generic.List[object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
Die Leistungsauswirkung der Verwendung der Arrayzugabe wächst exponentiell mit der Größe der Sammlung und den Zahlenzugaben. Mit diesem Code wird das explizite Zuweisen von Werten zu einem Array mit der Verwendung des Arrays und die Verwendung der Add(T)
-Methode für ein [List<T>]
-Objekt verglichen. Sie definiert die explizite Zuordnung als Basisplan für die Leistung.
$tests = @{
'PowerShell Explicit Assignment' = {
param($count)
$result = foreach($i in 1..$count) {
$i
}
}
'.Add(T) to List<T>' = {
param($count)
$result = [Collections.Generic.List[int]]::new()
foreach($i in 1..$count) {
$result.Add($i)
}
}
'+= Operator to Array' = {
param($count)
$result = @()
foreach($i in 1..$count) {
$result += $i
}
}
}
5kb, 10kb, 100kb | ForEach-Object {
$groupResult = foreach($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value -Count $_ }).TotalMilliseconds
[pscustomobject]@{
CollectionSize = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Diese Tests wurden auf einem Windows 11-Computer in PowerShell 7.3.4 ausgeführt.
CollectionSize Test TotalMilliseconds RelativeSpeed
-------------- ---- ----------------- -------------
5120 PowerShell Explicit Assignment 26.65 1x
5120 .Add(T) to List<T> 110.98 4.16x
5120 += Operator to Array 402.91 15.12x
10240 PowerShell Explicit Assignment 0.49 1x
10240 .Add(T) to List<T> 137.67 280.96x
10240 += Operator to Array 1678.13 3424.76x
102400 PowerShell Explicit Assignment 11.18 1x
102400 .Add(T) to List<T> 1384.03 123.8x
102400 += Operator to Array 201991.06 18067.18x
Wenn Sie mit großen Sammlungen arbeiten, ist das Hinzufügen von Arrays erheblich langsamer als das Hinzufügen zu einer List<T>
.
Wenn Sie ein [List<T>]
-Objekt verwenden, müssen Sie die Liste mit einem bestimmten Typ erstellen, z. B. [String]
oder [Int]
. Wenn Sie der Liste Objekte eines anderen Typs hinzufügen, werden sie in den angegebenen Typ umgegossen. Wenn sie nicht in den angegebenen Typ umwandeln können, löst die Methode eine Ausnahme aus.
$intList = [System.Collections.Generic.List[int]]::new()
$intList.Add(1)
$intList.Add('2')
$intList.Add(3.0)
$intList.Add('Four')
$intList
MethodException:
Line |
5 | $intList.Add('Four')
| ~~~~~~~~~~~~~~~~~~~~
| Cannot convert argument "item", with value: "Four", for "Add" to type
"System.Int32": "Cannot convert value "Four" to type "System.Int32".
Error: "The input string 'Four' was not in a correct format.""
1
2
3
Wenn die Liste eine Sammlung verschiedener Objekttypen sein muss, erstellen Sie sie mit [Object]
als Listentyp. Sie können die Auflistung auflisten, um die Typen der darin enthaltenen Objekte zu prüfen.
$objectList = [System.Collections.Generic.List[object]]::new()
$objectList.Add(1)
$objectList.Add('2')
$objectList.Add(3.0)
$objectList | ForEach-Object { "$_ is $($_.GetType())" }
1 is int
2 is string
3 is double
Wenn Sie ein Array benötigen, können Sie die ToArray()
-Methode in der Liste aufrufen oder PowerShell das Array für Sie erstellen lassen:
$results = @(
Get-Something
Get-SomethingElse
)
In diesem Beispiel erstellt PowerShell eine [ArrayList]
, um die Ergebnisse in der Pipeline innerhalb des Arrayausdrucks zu enthalten. Bevor Sie $results
zuweisen, konvertiert PowerShell die [ArrayList]
in eine [Object[]]
.
Zeichenfolgenzugabe
Zeichenfolgen sind unveränderlich. Jede Ergänzung zur Zeichenfolge erstellt tatsächlich eine neue Zeichenfolge, die groß genug ist, um den Inhalt der linken und rechten Operanden zu halten, und kopiert dann die Elemente beider Operanden in die neue Zeichenfolge. Bei kleinen Zeichenfolgen spielt dieser Aufwand möglicherweise keine Rolle. Bei großen Zeichenfolgen kann sich dies auf die Leistung und den Arbeitsspeicherverbrauch auswirken.
Es gibt mindestens zwei Alternativen:
- Der
-join
-Operator verkettet Zeichenfolgen - Die .NET-
[StringBuilder]
-Klasse stellt eine veränderbare Zeichenfolge bereit.
Im folgenden Beispiel wird die Leistung dieser drei Methoden zum Erstellen einer Zeichenfolge verglichen.
$tests = @{
'StringBuilder' = {
$sb = [System.Text.StringBuilder]::new()
foreach ($i in 0..$args[0]) {
$sb = $sb.AppendLine("Iteration $i")
}
$sb.ToString()
}
'Join operator' = {
$string = @(
foreach ($i in 0..$args[0]) {
"Iteration $i"
}
) -join "`n"
$string
}
'Addition Assignment +=' = {
$string = ''
foreach ($i in 0..$args[0]) {
$string += "Iteration $i`n"
}
$string
}
}
10kb, 50kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = (Measure-Command { & $test.Value $_ }).TotalMilliseconds
[pscustomobject]@{
Iterations = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms, 2)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Diese Tests wurden auf einem Windows 11-Computer in PowerShell 7.4.2 ausgeführt. Die Ausgabe zeigt, dass der -join
-Operator am schnellsten ist, gefolgt von der [StringBuilder]
Klasse.
Iterations Test TotalMilliseconds RelativeSpeed
---------- ---- ----------------- -------------
10240 Join operator 14.75 1x
10240 StringBuilder 62.44 4.23x
10240 Addition Assignment += 619.64 42.01x
51200 Join operator 43.15 1x
51200 StringBuilder 304.32 7.05x
51200 Addition Assignment += 14225.13 329.67x
102400 Join operator 85.62 1x
102400 StringBuilder 499.12 5.83x
102400 Addition Assignment += 67640.79 790.01x
Die Zeiten und relativen Geschwindigkeiten können je nach Hardware, Version von PowerShell und der aktuellen Workload auf dem System variieren.
Verarbeiten großer Dateien
Die idiomatische Methode zum Verarbeiten einer Datei in PowerShell könnte etwa wie folgt aussehen:
Get-Content $path | Where-Object Length -GT 10
Dies kann eine Größenordnung langsamer sein als .NET-APIs direkt zu verwenden. Sie können z. B. die .NET-[StreamReader]
-Klasse verwenden:
try {
$reader = [System.IO.StreamReader]::new($path)
while (-not $reader.EndOfStream) {
$line = $reader.ReadLine()
if ($line.Length -gt 10) {
$line
}
}
}
finally {
if ($reader) {
$reader.Dispose()
}
}
Sie können auch die ReadLines
Methode von [System.IO.File]
verwenden, die StreamReader
umschließt, vereinfacht den Lesevorgang:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Nachschlagen von Einträgen nach Eigenschaft in großen Sammlungen
Es ist üblich, dass Sie eine freigegebene Eigenschaft verwenden müssen, um denselben Datensatz in verschiedenen Sammlungen zu identifizieren, z. B. einen Namen zum Abrufen einer ID aus einer Liste und einer E-Mail aus einer anderen. Das Durchlaufen der ersten Liste, um den übereinstimmenden Datensatz in der zweiten Sammlung zu finden, ist langsam. Insbesondere hat die wiederholte Filterung der zweiten Sammlung einen großen Aufwand.
Bei zwei Auflistungen, eine mit einer -ID und Name, die andere mit Name und E-Mail-:
$Employees = 1..10000 | ForEach-Object {
[PSCustomObject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[PSCustomObject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
Die übliche Methode zum Abgleichen dieser Auflistungen zum Zurückgeben einer Liste von Objekten mit der -ID, Nameund Eigenschaften für E-Mail- könnte wie folgt aussehen:
$Results = $Employees | ForEach-Object -Process {
$Employee = $_
$Account = $Accounts | Where-Object -FilterScript {
$_.Name -eq $Employee.Name
}
[pscustomobject]@{
Id = $Employee.Id
Name = $Employee.Name
Email = $Account.Email
}
}
Diese Implementierung muss jedoch alle 5000 Elemente in der $Accounts
Auflistung einmal nach jedem Element in der $Employee
-Auflistung filtern. Dies kann auch für diesen Einzelwert-Nachschlagevorgang Minuten dauern.
Stattdessen können Sie eine Hashtabelle erstellen, die die freigegebene Name Eigenschaft als Schlüssel und das übereinstimmende Konto als Wert verwendet.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Das Nachschlagen von Schlüsseln in einer Hashtabelle ist viel schneller als das Filtern einer Auflistung nach Eigenschaftswerten. Statt jedes Element in der Auflistung zu überprüfen, kann PowerShell überprüfen, ob der Schlüssel definiert ist und seinen Wert verwendet.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Dies ist viel schneller. Während der Schleifenfilter Minuten dauerte, dauert die Hashsuche weniger als eine Sekunde.
Write-Host sorgfältig verwenden
Der Befehl Write-Host
sollte nur verwendet werden, wenn Sie formatierten Text in die Hostkonsole schreiben müssen, anstatt Objekte in die Success Pipeline zu schreiben.
Write-Host
kann eine Größenordnung langsamer sein als [Console]::WriteLine()
für bestimmte Hosts wie pwsh.exe
, powershell.exe
oder powershell_ise.exe
.
[Console]::WriteLine()
funktioniert jedoch nicht garantiert in allen Hosts. Außerdem wird die Ausgabe, die mit [Console]::WriteLine()
geschrieben wurde, nicht in Transkriptionen geschrieben, die von Start-Transcript
gestartet werden.
JIT-Kompilierung
PowerShell kompiliert den Skriptcode in Bytecode, der interpretiert wird. Ab PowerShell 3 kann PowerShell für Code, der wiederholt in einer Schleife ausgeführt wird, die Leistung verbessern, indem just-in-time (JIT) den Code in systemeigenem Code kompiliert.
Schleifen mit weniger als 300 Anweisungen sind für die JIT-Kompilierung geeignet. Schleifen sind größer als die zu kostspielige Kompilierung. Wenn die Schleife 16 Mal ausgeführt wurde, wird das Skript IM Hintergrund kompiliert. Nach Abschluss der JIT-Kompilierung wird die Ausführung an den kompilierten Code übertragen.
Vermeiden wiederholter Aufrufe einer Funktion
Das Aufrufen einer Funktion kann ein teurer Vorgang sein. Wenn Sie eine Funktion in einer lang ausgeführten engen Schleife aufrufen, sollten Sie die Schleife innerhalb der Funktion verschieben.
Betrachten Sie die folgenden Beispiele:
$tests = @{
'Simple for-loop' = {
param([int] $RepeatCount, [random] $RanGen)
for ($i = 0; $i -lt $RepeatCount; $i++) {
$null = $RanGen.Next()
}
}
'Wrapped in a function' = {
param([int] $RepeatCount, [random] $RanGen)
function Get-RandomNumberCore {
param ($rng)
$rng.Next()
}
for ($i = 0; $i -lt $RepeatCount; $i++) {
$null = Get-RandomNumberCore -rng $RanGen
}
}
'for-loop in a function' = {
param([int] $RepeatCount, [random] $RanGen)
function Get-RandomNumberAll {
param ($rng, $count)
for ($i = 0; $i -lt $count; $i++) {
$null = $rng.Next()
}
}
Get-RandomNumberAll -rng $RanGen -count $RepeatCount
}
}
5kb, 10kb, 100kb | ForEach-Object {
$rng = [random]::new()
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = Measure-Command { & $test.Value -RepeatCount $_ -RanGen $rng }
[pscustomobject]@{
CollectionSize = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms.TotalMilliseconds,2)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Das beispiel Basic for-loop ist die Basislinie für die Leistung. Im zweiten Beispiel wird der Zufallszahlengenerator in eine Funktion umbrochen, die in einer engen Schleife aufgerufen wird. Im dritten Beispiel wird die Schleife innerhalb der Funktion verschoben. Die Funktion wird nur einmal aufgerufen, aber der Code generiert immer noch die gleiche Menge an Zufallszahlen. Beachten Sie die Unterschiede bei den Ausführungszeiten für jedes Beispiel.
CollectionSize Test TotalMilliseconds RelativeSpeed
-------------- ---- ----------------- -------------
5120 for-loop in a function 9.62 1x
5120 Simple for-loop 10.55 1.1x
5120 Wrapped in a function 62.39 6.49x
10240 Simple for-loop 17.79 1x
10240 for-loop in a function 18.48 1.04x
10240 Wrapped in a function 127.39 7.16x
102400 for-loop in a function 179.19 1x
102400 Simple for-loop 181.58 1.01x
102400 Wrapped in a function 1155.57 6.45x
Vermeiden des Umbruchs von Cmdlet-Pipelines
Die meisten Cmdlets werden für die Pipeline implementiert, bei der es sich um eine sequenzielle Syntax und einen Prozess handelt. Zum Beispiel:
cmdlet1 | cmdlet2 | cmdlet3
Die Initialisierung einer neuen Pipeline kann teuer sein, daher sollten Sie vermeiden, eine Cmdlet-Pipeline in eine andere vorhandene Pipeline umzuschließen.
Betrachten Sie das folgende Beispiel. Die Input.csv
Datei enthält 2100 Zeilen. Der Befehl Export-Csv
wird in die ForEach-Object
Pipeline eingeschlossen. Das cmdlet Export-Csv
wird für jede Iteration der ForEach-Object
Schleife aufgerufen.
$measure = Measure-Command -Expression {
Import-Csv .\Input.csv | ForEach-Object -Begin { $Id = 1 } -Process {
[PSCustomObject]@{
Id = $Id
Name = $_.opened_by
} | Export-Csv .\Output1.csv -Append
}
}
'Wrapped = {0:N2} ms' -f $measure.TotalMilliseconds
Wrapped = 15,968.78 ms
Im nächsten Beispiel wurde der Befehl Export-Csv
außerhalb der ForEach-Object
Pipeline verschoben.
In diesem Fall wird Export-Csv
nur einmal aufgerufen, verarbeitet aber weiterhin alle Objekte, die aus ForEach-Object
übergeben wurden.
$measure = Measure-Command -Expression {
Import-Csv .\Input.csv | ForEach-Object -Begin { $Id = 2 } -Process {
[PSCustomObject]@{
Id = $Id
Name = $_.opened_by
}
} | Export-Csv .\Output2.csv
}
'Unwrapped = {0:N2} ms' -f $measure.TotalMilliseconds
Unwrapped = 42.92 ms
Das unwrappte Beispiel ist 372 Mal schneller. Beachten Sie außerdem, dass für die erste Implementierung der parameter "Append" erforderlich ist, der für die spätere Implementierung nicht erforderlich ist.
Objekterstellung
Das Erstellen von Objekten mithilfe des Cmdlets New-Object
kann langsam sein. Der folgende Code vergleicht die Leistung des Erstellens von Objekten mithilfe des cmdlets New-Object
mit der zugriffstaste [pscustomobject]
.
Measure-Command {
$test = 'PSCustomObject'
for ($i = 0; $i -lt 100000; $i++) {
$resultObject = [PSCustomObject]@{
Name = 'Name'
Path = 'FullName'
}
}
} | Select-Object @{n='Test';e={$test}},TotalSeconds
Measure-Command {
$test = 'New-Object'
for ($i = 0; $i -lt 100000; $i++) {
$resultObject = New-Object -TypeName PSObject -Property @{
Name = 'Name'
Path = 'FullName'
}
}
} | Select-Object @{n='Test';e={$test}},TotalSeconds
Test TotalSeconds
---- ------------
PSCustomObject 0.48
New-Object 3.37
PowerShell 5.0 hat die new()
statische Methode für alle .NET-Typen hinzugefügt. Der folgende Code vergleicht die Leistung des Erstellens von Objekten mithilfe des cmdlets New-Object
mit der new()
-Methode.
Measure-Command {
$test = 'new() method'
for ($i = 0; $i -lt 100000; $i++) {
$sb = [System.Text.StringBuilder]::new(1000)
}
} | Select-Object @{n='Test';e={$test}},TotalSeconds
Measure-Command {
$test = 'New-Object'
for ($i = 0; $i -lt 100000; $i++) {
$sb = New-Object -TypeName System.Text.StringBuilder -ArgumentList 1000
}
} | Select-Object @{n='Test';e={$test}},TotalSeconds
Test TotalSeconds
---- ------------
new() method 0.59
New-Object 3.17
Verwenden von OrderedDictionary zum dynamischen Erstellen neuer Objekte
Es gibt Situationen, in denen objekte basierend auf einigen Eingaben dynamisch erstellt werden müssen, die vielleicht am häufigsten verwendete Methode zum Erstellen einer neuen PSObject- und dann neue Eigenschaften mithilfe des cmdlets Add-Member
hinzufügen. Die Leistungskosten für kleine Sammlungen, die diese Technik verwenden, können jedoch für große Sammlungen sehr spürbar werden. In diesem Fall empfiehlt sich die Verwendung eines [OrderedDictionary]
und anschließendes Konvertieren in ein PSObject- mithilfe der Zugriffstaste [pscustomobject]
Typs. Weitere Informationen finden Sie im Abschnitt Erstellen von geordneten Wörterbüchern Abschnitt about_Hash_Tables.
Gehen Sie davon aus, dass die folgende API-Antwort in der Variablen $json
gespeichert ist.
{
"tables": [
{
"name": "PrimaryResult",
"columns": [
{ "name": "Type", "type": "string" },
{ "name": "TenantId", "type": "string" },
{ "name": "count_", "type": "long" }
],
"rows": [
[ "Usage", "63613592-b6f7-4c3d-a390-22ba13102111", "1" ],
[ "Usage", "d436f322-a9f4-4aad-9a7d-271fbf66001c", "1" ],
[ "BillingFact", "63613592-b6f7-4c3d-a390-22ba13102111", "1" ],
[ "BillingFact", "d436f322-a9f4-4aad-9a7d-271fbf66001c", "1" ],
[ "Operation", "63613592-b6f7-4c3d-a390-22ba13102111", "7" ],
[ "Operation", "d436f322-a9f4-4aad-9a7d-271fbf66001c", "5" ]
]
}
]
}
Angenommen, Sie möchten diese Daten in eine CSV-Datei exportieren. Zuerst müssen Sie neue Objekte erstellen und die Eigenschaften und Werte mithilfe des Cmdlets Add-Member
hinzufügen.
$data = $json | ConvertFrom-Json
$columns = $data.tables.columns
$result = foreach ($row in $data.tables.rows) {
$obj = [psobject]::new()
$index = 0
foreach ($column in $columns) {
$obj | Add-Member -MemberType NoteProperty -Name $column.name -Value $row[$index++]
}
$obj
}
Mit einem OrderedDictionary
kann der Code in Folgendes übersetzt werden:
$data = $json | ConvertFrom-Json
$columns = $data.tables.columns
$result = foreach ($row in $data.tables.rows) {
$obj = [ordered]@{}
$index = 0
foreach ($column in $columns) {
$obj[$column.name] = $row[$index++]
}
[pscustomobject] $obj
}
In beiden Fällen wäre die $result
Ausgabe gleich:
Type TenantId count_
---- -------- ------
Usage 63613592-b6f7-4c3d-a390-22ba13102111 1
Usage d436f322-a9f4-4aad-9a7d-271fbf66001c 1
BillingFact 63613592-b6f7-4c3d-a390-22ba13102111 1
BillingFact d436f322-a9f4-4aad-9a7d-271fbf66001c 1
Operation 63613592-b6f7-4c3d-a390-22ba13102111 7
Operation d436f322-a9f4-4aad-9a7d-271fbf66001c 5
Der letztere Ansatz wird exponentiell effizienter, da die Anzahl der Objekte und Membereigenschaften steigt.
Hier ist ein Leistungsvergleich von drei Techniken zum Erstellen von Objekten mit 5 Eigenschaften:
$tests = @{
'[ordered] into [pscustomobject] cast' = {
param([int] $iterations, [string[]] $props)
foreach ($i in 1..$iterations) {
$obj = [ordered]@{}
foreach ($prop in $props) {
$obj[$prop] = $i
}
[pscustomobject] $obj
}
}
'Add-Member' = {
param([int] $iterations, [string[]] $props)
foreach ($i in 1..$iterations) {
$obj = [psobject]::new()
foreach ($prop in $props) {
$obj | Add-Member -MemberType NoteProperty -Name $prop -Value $i
}
$obj
}
}
'PSObject.Properties.Add' = {
param([int] $iterations, [string[]] $props)
# this is how, behind the scenes, `Add-Member` attaches
# new properties to our PSObject.
# Worth having it here for performance comparison
foreach ($i in 1..$iterations) {
$obj = [psobject]::new()
foreach ($prop in $props) {
$obj.PSObject.Properties.Add(
[psnoteproperty]::new($prop, $i))
}
$obj
}
}
}
$properties = 'Prop1', 'Prop2', 'Prop3', 'Prop4', 'Prop5'
1kb, 10kb, 100kb | ForEach-Object {
$groupResult = foreach ($test in $tests.GetEnumerator()) {
$ms = Measure-Command { & $test.Value -iterations $_ -props $properties }
[pscustomobject]@{
Iterations = $_
Test = $test.Key
TotalMilliseconds = [math]::Round($ms.TotalMilliseconds, 2)
}
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
}
$groupResult = $groupResult | Sort-Object TotalMilliseconds
$groupResult | Select-Object *, @{
Name = 'RelativeSpeed'
Expression = {
$relativeSpeed = $_.TotalMilliseconds / $groupResult[0].TotalMilliseconds
[math]::Round($relativeSpeed, 2).ToString() + 'x'
}
}
}
Und dies sind die Ergebnisse:
Iterations Test TotalMilliseconds RelativeSpeed
---------- ---- ----------------- -------------
1024 [ordered] into [pscustomobject] cast 22.00 1x
1024 PSObject.Properties.Add 153.17 6.96x
1024 Add-Member 261.96 11.91x
10240 [ordered] into [pscustomobject] cast 65.24 1x
10240 PSObject.Properties.Add 1293.07 19.82x
10240 Add-Member 2203.03 33.77x
102400 [ordered] into [pscustomobject] cast 639.83 1x
102400 PSObject.Properties.Add 13914.67 21.75x
102400 Add-Member 23496.08 36.72x