Zagadnienia dotyczące wydajności skryptów programu PowerShell
Skrypty programu PowerShell, które bezpośrednio wykorzystują program .NET i unikają potoku, są szybsze niż idiomatyczny program PowerShell. Idiomatyczny program PowerShell zwykle używa poleceń cmdlet i funkcji programu PowerShell w dużym stopniu, często wykorzystując potok i porzucając go do środowiska .NET tylko wtedy, gdy jest to konieczne.
Uwaga
Wiele technik opisanych w tym miejscu nie jest idiomatycznych programu PowerShell i może zmniejszyć czytelność skryptu programu PowerShell. Autorom skryptów zaleca się używanie idiomatycznego programu PowerShell, chyba że wydajność nie dyktuje inaczej.
Pomijanie danych wyjściowych
Istnieje wiele sposobów, aby uniknąć zapisywania obiektów w potoku:
$null = $arrayList.Add($item)
[void]$arrayList.Add($item)
Przypisanie do lub rzutowanie do są w przybliżeniu $null
równoważne i powinny być zazwyczaj [void]
preferowane, gdy wydajność ma znaczenie.
$arrayList.Add($item) > $null
Przekierowywanie plików do jest niemal tak dobre jak poprzednie alternatywy, większość skryptów nigdy $null
nie zauważy różnicy. Jednak w zależności od scenariusza przekierowywanie plików powoduje niewielkie obciążenie.
$arrayList.Add($item) | Out-Null
Można również potokować do Out-Null
. W programie PowerShell 7.x jest to nieco wolniejsze niż przekierowywanie, ale prawdopodobnie nie jest zauważalne w przypadku większości skryptów. Jednak wywoływanie w dużej pętli może być Out-Null
znacznie wolniejsze, nawet w programie PowerShell 7.x.
$d = Get-Date
Measure-Command { for($i=0; $i -lt 1mb; $i++) { $null=$d } } |
Select-Object TotalSeconds
TotalSeconds
------------
1.0549325
$d = Get-Date
Measure-Command { for($i=0; $i -lt 1mb; $i++) { $d | Out-Null } } |
Select-Object TotalSeconds
TotalSeconds
------------
5.9572186
Windows PowerShell 5.1 nie ma tych samych optymalizacji dla programu PowerShell 7.x, dlatego należy unikać używania w kodzie Out-Null
Out-Null
wrażliwym na wydajność.
Wprowadzenie bloku skryptu i wywołanie go (przy użyciu pozyskiwania kropek lub w inny sposób), a następnie przypisanie wyniku do jest wygodną techniką pomijania danych wyjściowych dużego $null
bloku skryptu.
$null = . {
$arrayList.Add($item)
$arrayList.Add(42)
}
Ta technika działa mniej więcej tak dobrze, jak przekierowywuje dane do i należy unikać ich Out-Null
w skryptach wrażliwych na wydajność. Dodatkowe obciążenie w tym przykładzie wynika z utworzenia i wywołania bloku skryptu, który był wcześniej skryptem w tekście.
Dodawanie tablicy
Generowanie listy elementów jest często wykonywane przy użyciu tablicy z operatorem dodatku:
$results = @()
$results += Do-Something
$results += Do-SomethingElse
$results
Może to być bardzo niewydajne, ponieważ tablice są niezmienne. Każdy dodatek do tablicy faktycznie tworzy nową tablicę wystarczająco dużą, aby pomieścić wszystkie elementy lewego i prawego operandu, a następnie kopiuje elementy obu operandów do nowej tablicy. W przypadku małych kolekcji ten narzut może nie mieć znaczenia. W przypadku dużych kolekcji na pewno może to być problemem.
Istnieje kilka alternatyw. Jeśli w rzeczywistości nie potrzebujesz tablicy, rozważ użycie tablicy ArrayList:
$results = [System.Collections.ArrayList]::new()
$results.AddRange((Do-Something))
$results.AddRange((Do-SomethingElse))
$results
Jeśli potrzebujesz tablicy, możesz użyć własnej i po prostu wywołać element ArrayList
ArrayList.ToArray
, gdy chcesz, aby tablica. Alternatywnie możesz pozwolić programowi PowerShell na utworzenie ArrayList
poleceń i Array
dla Ciebie:
$results = @(
Do-Something
Do-SomethingElse
)
W tym przykładzie program PowerShell tworzy element do przechowywania wyników zapisywanych w ArrayList
potoku wewnątrz wyrażenia tablicy. Tuż przed przypisaniem do $results
polecenia program PowerShell konwertuje ArrayList
element na element object[]
.
Dodawanie ciągu
Podobnie jak tablice, ciągi są niezmienne. Każdy dodatek do ciągu faktycznie tworzy nowy ciąg wystarczająco duży, aby pomieścić zawartość lewego i prawego operandu, a następnie kopiuje elementy obu operandów do nowego ciągu. W przypadku małych ciągów ten narzut może nie mieć znaczenia. W przypadku dużych ciągów na pewno może to być problemem.
$string = ''
Measure-Command {
foreach( $i in 1..10000)
{
$string += "Iteration $i`n"
}
$string
} | Select-Object TotalMilliseconds
TotalMilliseconds
-----------------
641.8168
Istnieje kilka alternatyw. Do połączenia -join
ciągów można użyć operatora .
Measure-Command {
$string = @(
foreach ($i in 1..10000) { "Iteration $i" }
) -join "`n"
$string
} | Select-Object TotalMilliseconds
TotalMilliseconds
-----------------
22.7069
W tym przykładzie użycie -join
operatora jest prawie 30-krotnie szybsze niż dodawanie ciągu.
Można również użyć klasy .NET StringBuilder.
$sb = [System.Text.StringBuilder]::new()
Measure-Command {
foreach( $i in 1..10000)
{
[void]$sb.Append("Iteration $i`n")
}
$sb.ToString()
} | Select-Object TotalMilliseconds
TotalMilliseconds
-----------------
13.4671
W tym przykładzie użycie StringBuilder jest prawie 50 razy szybsze niż dodawanie ciągu.
Przetwarzanie dużych plików
Idiomatyczny sposób przetwarzania pliku w programie PowerShell może wyglądać podobnie do:
Get-Content $path | Where-Object { $_.Length -gt 10 }
Może to być prawie o rząd wielkości wolniejsze niż bezpośrednie korzystanie z interfejsów API .NET:
try
{
$stream = [System.IO.StreamReader]::new($path)
while ($line = $stream.ReadLine())
{
if ($line.Length -gt 10)
{
$line
}
}
}
finally
{
$stream.Dispose()
}
Unikaj Write-Host
Ogólnie uważa się, że zapis danych wyjściowych bezpośrednio w konsoli jest uznawany za zły, ale gdy ma to sens, wiele skryptów używa polecenia Write-Host
.
Jeśli musisz zapisać wiele komunikatów w konsoli, element może być o rząd wielkości Write-Host
wolniejszy niż [Console]::WriteLine()
. Należy jednak pamiętać, że jest to odpowiednia alternatywa tylko dla [Console]::WriteLine()
określonych hostów, takich jak pwsh.exe
, lub powershell.exe
powershell_ise.exe
. Nie ma gwarancji, że będzie działać na wszystkich hostach. Ponadto dane wyjściowe zapisywane przy [Console]::WriteLine()
użyciu nie są zapisywane w transkrypcjach uruchomionych przez . Start-Transcript
Zamiast używać funkcji Write-Host
, rozważ użycie write-output.
Kompilacja JIT
Program PowerShell kompiluje kod skryptu do interpretowanych kodów bajtowych. Począwszy od programu PowerShell 3, w przypadku kodu, który jest wielokrotnie wykonywany w pętli, program PowerShell może zwiększyć wydajność przez kompilowanie kodu just in time (JIT) do kodu natywnego.
Pętle, które mają mniej niż 300 instrukcji, kwalifikują się do kompilacji JIT. Pętle większe niż te są zbyt kosztowne do skompilowania. Gdy pętla jest wykonywana 16 razy, skrypt jest kompilowany jit w tle. Po zakończeniu kompilacji JIT wykonywanie jest przenoszone do skompilowanego kodu.
Unikanie powtarzających się wywołań funkcji
Wywołanie funkcji może być kosztowną operacją. Jeśli funkcja jest wywoływana w długotrwałej ścisłej pętli, rozważ przeniesienie pętli wewnątrz funkcji.
Rozważmy następujące przykłady:
$ranGen = New-Object System.Random
$RepeatCount = 10000
'Basic for-loop = {0}ms' -f (Measure-Command -Expression {
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}).TotalMilliseconds
'Wrapped in a function = {0}ms' -f (Measure-Command -Expression {
function Get-RandNum_Core {
param ($ranGen)
$ranGen.Next()
}
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = Get-RandNum_Core $ranGen
}
}).TotalMilliseconds
'For-loop in a function = {0}ms' -f (Measure-Command -Expression {
function Get-RandNum_All {
param ($ranGen)
for ($i = 0; $i -lt $RepeatCount; $i++) {
$Null = $ranGen.Next()
}
}
Get-RandNum_All $ranGen
}).TotalMilliseconds
Przykład podstawowej pętli for jest linią bazową wydajności. Drugi przykład opakuje generator liczb losowych w funkcję, która jest wywoływana w ścisłej pętli. Trzeci przykład przenosi pętlę wewnątrz funkcji. Funkcja jest wywoływana tylko raz, ale kod nadal generuje 10000 liczb losowych. Zwróć uwagę na różnicę w czasie wykonywania dla każdego przykładu.
Basic for-loop = 47.8668ms
Wrapped in a function = 820.1396ms
For-loop in a function = 23.3193ms
Opinia
Prześlij i wyświetl opinię dla