A PowerShell-szkriptek teljesítményével kapcsolatos szempontok

A .NET-et közvetlenül használó és a folyamatot elkerülő PowerShell-szkriptek általában gyorsabbak, mint az idiomatikus PowerShell. Az idiomatikus PowerShell általában erősen használja a parancsmagokat és a PowerShell-függvényeket, gyakran használja a folyamatot, és csak szükség esetén vált le a .NET-be.

Megjegyzés

Az itt ismertetett technikák közül sok nem idiomatikus PowerShell, és csökkentheti a PowerShell-szkriptek olvashatóságát. A szkriptkészítőknek ajánlott idiomatikus PowerShellt használniuk, hacsak a teljesítmény másként nem rendelkezik.

Kimenet mellőzése

Számos módon kerülheti el, hogy objektumokat írjon a folyamatba:

$null = $arrayList.Add($item)
[void]$arrayList.Add($item)

A hozzárendelés vagy $null a [void] kiosztás nagyjából egyenértékű, és általában a teljesítmény fontossága esetén érdemes előnyben részesíteni.

$arrayList.Add($item) > $null

A fájlátirányítás $null majdnem olyan jó, mint az előző alternatívák, a legtöbb szkript soha nem észlelné a különbséget. A forgatókönyvtől függően azonban a fájlátirányítás némi többletterhelést jelent.

$arrayList.Add($item) | Out-Null

Azt is megteheti, hogy csövet .Out-Null A PowerShell 7.x-ben ez egy kicsit lassabb, mint az átirányítás, de a legtöbb szkript esetében valószínűleg nem észlelhető. Out-Null A nagy hurok hívása azonban jelentősen lassabb lehet, még a PowerShell 7.x-ben is.

$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 nem rendelkezik ugyanazokkal az optimalizálási lehetőségekkel, mint a Out-Null PowerShell 7.x esetében, ezért kerülje a teljesítményérzékeny kód használatátOut-Null.

Egy szkriptblokk bevezetésével és meghívásával (pont forráskezeléssel vagy más módon), majd az eredmény $null hozzárendelésével kényelmesen letiltható egy nagy méretű szkriptblokk kimenete.

$null = . {
    $arrayList.Add($item)
    $arrayList.Add(42)
}

Ez a technika nagyjából ugyanúgy működik, mint a csővezetékek, Out-Null és a teljesítményérzékeny szkriptekben el kell kerülni. A példában szereplő többletterhelés egy korábban beágyazott szkriptblokk létrehozásából és meghívásából ered.

Tömb összeadása

Az elemek listájának létrehozása gyakran történik egy összeadás operátorral rendelkező tömb használatával:

$results = @()
$results += Do-Something
$results += Do-SomethingElse
$results

Ez nagyon nem hatékony lehet, mert a tömbök nem módosíthatók. A tömb minden egyes hozzáadása létrehoz egy új tömböt, amely elég nagy ahhoz, hogy a bal és a jobb operandus összes elemét tárolja, majd mindkét operandus elemeit átmásolja az új tömbbe. Kis gyűjtemények esetében ez a többletterhelés nem feltétlenül számít. Nagy gyűjtemények esetén ez minden bizonnyal problémát okozhat.

Van néhány alternatíva. Ha valójában nincs szüksége tömbre, fontolja meg egy ArrayList használatát:

$results = [System.Collections.ArrayList]::new()
$results.AddRange((Do-Something))
$results.AddRange((Do-SomethingElse))
$results

Ha szüksége van tömbre, használhatja a sajátját ArrayList , és egyszerűen meghívhatja ArrayList.ToArray , amikor a tömböt szeretné. Másik lehetőségként engedélyezheti, hogy a PowerShell létrehozza a ArrayList következőt Array :

$results = @(
    Do-Something
    Do-SomethingElse
)

Ebben a példában a PowerShell létrehoz egy olyan ArrayList objektumot, amely a tömbkifejezésen belül tárolja a folyamatba írt eredményeket. A Hozzárendelés $resultselőtt a PowerShell átalakítja a ArrayList elemet egy object[].

Sztringek összeadása

A tömbökhöz hasonlóan a sztringek sem módosíthatók. A sztring minden egyes kiegészítése létrehoz egy új sztringet, amely elég nagy ahhoz, hogy a bal és a jobb operandus tartalmát is tartalmazza, majd mindkét operandus elemeit átmásolja az új sztringbe. Kis sztringek esetén ez a többletterhelés nem feltétlenül számít. Nagy sztringek esetén ez minden bizonnyal problémát okozhat.

$string = ''
Measure-Command {
      foreach( $i in 1..10000)
      {
          $string += "Iteration $i`n"
      }
      $string
  } | Select-Object TotalMilliseconds

TotalMilliseconds
-----------------
         641.8168

Van néhány alternatíva. Az operátorral -join sztringeket fűzhet össze.

Measure-Command {
      $string = @(
          foreach ($i in 1..10000) { "Iteration $i" }
      ) -join "`n"
      $string
  } | Select-Object TotalMilliseconds

TotalMilliseconds
-----------------
          22.7069

Ebben a példában az -join operátor használata közel 30-szor gyorsabb, mint a sztringek összeadása.

A .NET StringBuilder osztályt is használhatja.

$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

Ebben a példában a StringBuilder használata közel 50-szer gyorsabb, mint a sztringek hozzáadása.

Nagyméretű fájlok feldolgozása

A fájlok PowerShellben történő feldolgozásának idiomatikus módja az alábbihoz hasonló lehet:

Get-Content $path | Where-Object { $_.Length -gt 10 }

Ez közel nagyságrendekkel lassabb lehet, mint a .NET API-k közvetlen használata:

try
{
    $stream = [System.IO.StreamReader]::new($path)
    while ($line = $stream.ReadLine())
    {
        if ($line.Length -gt 10)
        {
            $line
        }
    }
}
finally
{
    $stream.Dispose()
}

Kerülje a Write-Host

Általában rossz gyakorlat a kimenet közvetlenül a konzolra írása, de ha van értelme, sok szkript használja Write-Host.

Ha sok üzenetet kell írnia a konzolra, Write-Host nagyságrendekkel lassabb lehet, mint [Console]::WriteLine()a . Ne feledje azonban, hogy [Console]::WriteLine() ez csak egy megfelelő alternatíva bizonyos gazdagépek, például pwsh.exea , powershell.exevagy powershell_ise.exe. Nem garantált, hogy minden gazdagépen működik. Emellett a használatával [Console]::WriteLine() írt kimenet nem lesz megírva az átiratok által Start-Transcriptindított.

A használat Write-Hosthelyett fontolja meg a Write-Output használatát.

JIT-fordítás

A PowerShell lefordítja a szkriptkódot az értelmezett bájtkódra. A PowerShell 3-tól kezdve a ciklusban ismétlődően végrehajtott kódok esetében a PowerShell az igény szerinti (JIT) kód natív kódra fordításával javíthatja a teljesítményt.

A 300-nál kevesebb utasítást tartalmazó hurkok jiT-fordításra jogosultak. Az ennél nagyobb hurkok fordítása túl költséges. Amikor a hurok 16 alkalommal lett végrehajtva, a szkript JIT-fordításra kerül a háttérben. A JIT-fordítás befejezése után a végrehajtás átkerül a lefordított kódba.

A függvények ismételt hívásának elkerülése

A függvények meghívása költséges művelet lehet. Ha hosszú ideig futó szoros hurokban hív meg egy függvényt, fontolja meg a hurok áthelyezését a függvényen belül.

Tekintse meg a következő példákat:

$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

Az alapszintű for-loop példa a teljesítmény alapvonala. A második példa egy szoros hurokban meghívott függvénybe burkolja a véletlenszám-generátort. A harmadik példa a függvényen belülre helyezi a hurkot. A függvény csak egyszer van meghívva, de a kód továbbra is 10000 véletlenszerű számot generál. Figyelje meg az egyes példák végrehajtási idejének különbségét.

Basic for-loop = 47.8668ms
Wrapped in a function = 820.1396ms
For-loop in a function = 23.3193ms