Arrays als mögliches Geschwindigkeitsproblem in PowerShell Scripten
2024-11-25Mein Powershell Script läuft so lange! Das ist ein Satz den ich immer wieder höre. Sehr häufig finde ich dann in dem Script mindestens eine Zeile $myArray += $Item
.
Du denkst jetzt villeicht, was soll das den damit zu tun haben?
Aber Tatsache ist es hat damit zu tun und ich erkläre dir hier warum das so ist.
Arrays in PowerShell
Der Performancekiller
Die folgende Codekonstruktion ist oft in PowerShell-Skripten sehe und bei der Verwendung von ChatGPT oder GitHub Copilot wird häufig solcher Code generiert:
$myArray=@()
Foreach($object in $objects){
# do some stuff with the object
If($object.SomeProperty -match '^regular expression'){
$myArray += $Object
}
}
Wenn du nur mit wenigen Objekten arbeitest, kannst du das ohne merkliche negative Auswirkungen verwenden. Wenn du jedoch mit vielen Objekten arbeitest, ist diese Verwendung eines Arrays schrecklich.
Dies liegt daran, dass ein Standard-Array in PowerShell kein dynamischer Datentyp ist. Daher muss der Computer alle Daten aus dem vorhandenen Array plus das neue Objekt in ein neues Array und damit an einen neuen Ort kopieren. Dies ist eine zeitaufwändige Aktion.
Ein Beispiel aus dem echten Leben
Nehmen wir als Beispiel aus dem echten Leben einen Stapel Brennholz. Jetzt möchtest du ein Stück Brennholz zum Stapel hinzufügen. Aber das geht nicht, weil kein Platz für das zusätzliche Stück da ist.
Also legst du das neue Stück an einen neuen Ort und nimmst dann den vorhandenen Stapel und verschiebst ihn an den neuen Ort. Und jetzt wiederholst du das für die nächsten 10.000 Stücke Brennholz.
Würdest du das wirklich tun?
Der Performancegewinn
Wenn du ein dynamisches Array benötigst, in dem du Elemente hinzufügen und entfernen kannst, solltest du Generic.List anstelle eines statischen Arrays verwenden.
Wenn du keine dynamische Liste benötigst, kannst du die Leistung steigern, indem du den obigen Code wie folgt änderst:
$myArray=@(Foreach($object in $objects){
# do some stuff with the object
If($object.SomeProperty -match ‘^regular expression’){
$object # you can use Write-Output $object instead
}
})
Du glaubst mir nicht?
Teste es einfach selber mit dem folgenden Code:
Measure-Command {
$myArray1 = @()
foreach ($i in $(1..10000) ) {
$myArray1 += $i
}
}
Measure-Command {
$myArray2 = @(foreach ($z in $(1..10000) ) {
$z
})
}
Wenn ich das 100 mal in einer Schleife ausführe und berechne, wie viel schneller Variante 2 im Durchschnitt gegenüber Variante 1 ist, erhalte ich folgende Ergebnisse:
Bei 100 Durchläufen:
Variante2 ist im Durchschnitt 1399,76556 Millisekunden schneller als Variante1
Die durchschnittliche Laufzeit für Variante1 beträgt 1407,03501 Millisekunden
Die durchschnittliche Laufzeit für Variante2 beträgt 7,26945 Millisekunden
Du musst jedoch bedenken, dass ich in diesem Test nur Ganzzahlen verwende, um das Array zu füllen. Wenn du mit einem größeren Datentyp wie Active Directory-Benutzern oder -Gruppen arbeitest, ist der Unterschied viel größer.
In Powershell kannst du eine Generic.List für Zeichenfolgen so erstellen:
$myDynamicArray = new-object "System.Collections.Generic.List[System.String]"
oder wenn du keinen Datentyp für die Liste angeben kannst verwende System.Object als Datentyp:
$myDynamicArray = New-Object "System.Collections.Generic.List[System.Object]"
Alternativ kannst du es auch so schreiben:
$myDynamicArray = New-Object System.Collections.Generic.List``1[System.String]
Der gesamte Test-Code:
$runs = 100
$numberOfObjects=10000
$objectsForTest = $(1..$numberOfObjects)
$TimediffArray=@(
for ($t = 0; $t -lt $runs; $t++) {
$CmdResult1 = Measure-Command {
$myArray1 = @()
foreach ($i in $objectsForTest ) {
$myArray1 += $i
}
Remove-Variable myArray1 -Force
}
$CmdResult2=Measure-Command {
$myArray2 = @(foreach ($z in $objectsForTest ) {
$z
})
Remove-Variable myArray2 -Force
}
new-object psobject -property @{
'Variant1'=$CmdResult1.TotalMilliseconds;
'Variant2'=$CmdResult2.TotalMilliseconds;
'Difference'=$CmdResult1.TotalMilliseconds - $CmdResult2.TotalMilliseconds
}
}
)
[double]$difference=0.0
[double]$Variant1=0.0
[double]$Variant2=0.0
foreach($diff in $TimediffArray){
$difference+=$diff.Difference
$Variant1+=$diff.Variant1
$Variant2+=$diff.Variant2
}
write-host "Bei $($TimediffArray.Count)Durchläufen:`r`nVariante2 ist im Durchschnitt $($difference/$TimediffArray.Count) Millisekunden schneller als Variante1`r`nDie durchschnittliche laufzeeit für Variante1 beträgt $($Variant1/$TimediffArray.Count) Millisekunden`r`nDie durchschnittliche Laufzeit für Variante2 beträgt $($Variant2/$TimediffArray.Count) Millisekunden"