Megosztás:


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 parancsmagokat és PowerShell-függvényeket használ, gyakran kihasználva a munkafolyamatot, és a .NET-re csak akkor támaszkodik, ha szükséges.

Jegyzet

Az itt ismertetett technikák közül számos 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 diktál.

Kimenet letiltása

Számos módon elkerülhető az, hogy objektumokat írjunk a csővezetékbe.

  • Hozzárendelés vagy fájl átirányítása $null
  • Átküldés a [void]
  • Cső a Out-Null

A $null hozzárendelésének sebessége, a [void]-ként történő átalakítás sebessége és a fájlátirányítás $null sebessége szinte azonos. A Out-Null nagy hurokban való meghívása azonban jelentősen lassabb lehet, különösen a PowerShell 5.1-ben.

$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'
        }
    }
}

Ezeket a teszteket Windows 11 rendszerű gépen futtatták a PowerShell 7.3.4-ben. Az eredmények az alábbiakban láthatók:

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

Az idő és a relatív sebesség a hardvertől, a PowerShell verziójától és a rendszer aktuális számítási feladataitól függően változhat.

Tömb hozzáadása

Az elemek listájának létrehozása gyakran egy tömb használatával történik, a hozzáadás operátorral:

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

Jegyzet

A PowerShell 7.5-ben a tömbök hozzáadása optimalizálva lett, és többé nem hoz létre új tömböt az egyes műveletekhez. Az itt ismertetett teljesítménybeli megfontolások a 7.5-ös verzió előtti PowerShell-verziókra is érvényesek. További információ: A PowerShell 7.5 újdonságai.

A tömbök hozzáadása nem hatékony, mert a tömbök mérete rögzített. A tömb minden egyes hozzáadása létrehoz egy új tömböt, amely elég nagy ahhoz, hogy mind a bal, mind a jobb operandus összes elemét megtartsa. A rendszer mindkét operandus elemeit az új tömbbe másolja. A kis gyűjtemények esetében ez a többletterhelés nem feltétlenül számít. A nagy gyűjtemények teljesítménycsökkenést okozhatnak.

Van néhány alternatíva. Ha valójában nincs szüksége tömbre, fontolja meg egy beírt általános lista ([List<T>]) használatát:

$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results

A tömbök hozzáadásának teljesítményhatása exponenciálisan nő a gyűjtemény méretével és az összeadások számával. Ez a kód összehasonlítja az értékek tömbhöz való explicit hozzárendelését a tömbök összeadásával és a Add(T) metódus használatával egy [List<T>] objektumon. Egyértelmű hozzárendelést határoz meg a teljesítmény referencia alapjaként.

$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'
        }
    }
}

Ezeket a teszteket Windows 11 rendszerű gépen futtatták a PowerShell 7.3.4-ben.

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

Ha nagy gyűjteményekkel dolgozik, a tömbök hozzáadása jelentősen lassabb, mint a List<T>hozzáadása.

Ha [List<T>] objektumot használ, létre kell hoznia a listát egy adott típussal, például [string] vagy [int]. Ha más típusú objektumokat ad hozzá a listához, azok a megadott típusba kerülnek. Ha nem lehet a megadott típusra leadni őket, a metódus kivételt hoz létre.

$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

Ha azt szeretné, hogy a lista különböző típusú objektumok gyűjteménye legyen, hozza létre [Object] listatípusként. A gyűjtemény számbavételével megvizsgálhatja a benne lévő objektumok típusait.

$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

Ha tömbre van szüksége, meghívhatja a ToArray() metódust a listában, vagy engedélyezheti a PowerShell számára a tömb létrehozását:

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

Ebben a példában a PowerShell létrehoz egy [ArrayList], amely a tömbkifejezésen belül a folyamatba írt eredményeket tárolja. Mielőtt a -hoz rendelné, a PowerShell átalakítja a -vé.

Sztring hozzáadása

A sztringek nem módosíthatók. A sztring minden egyes hozzáadása létrehoz egy új sztringet, amely elég nagy ahhoz, hogy mind a bal, mind a jobb operandus tartalmát megtartsa, 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 karakterláncok esetén ez hatással lehet a teljesítményre és a memóriahasználatra.

Legalább két alternatíva létezik:

  • A -join operátor összefűzi a sztringeket
  • A .NET [StringBuilder] osztály módosítható karakterláncot biztosít.

Az alábbi példa a karakterláncok összeállításának három módszerét hasonlítja össze.

$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'
        }
    }
}

Ezeket a teszteket Windows 11 rendszerű gépen futtatták a PowerShell 7.4.2-ben. A kimenet azt mutatja, hogy a -join operátor a leggyorsabb, amelyet a [StringBuilder] osztály követ.

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

Az idő és a relatív sebesség a hardvertől, a PowerShell verziójától és a rendszer aktuális számítási feladataitól függően változhat.

Nagyméretű fájlok feldolgozása

A Fájlok PowerShellben történő feldolgozásának idiomatikus módja a következőhöz hasonló lehet:

Get-Content $path | Where-Object Length -GT 10

Ez nagyságrenddel lassabb lehet, mint a .NET API-k közvetlen használata. Használhatja például a .NET [StreamReader] osztályt:

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()
    }
}

A ReadLines[System.IO.File] metódusát is használhatja, amely StreamReaderkörbefuttatva egyszerűsíti az olvasási folyamatot:

foreach ($line in [System.IO.File]::ReadLines($path)) {
    if ($line.Length -gt 10) {
        $line
    }
}

Bejegyzések megkeresése tulajdonság szerint nagy gyűjteményekben

Gyakran előfordul, hogy egy megosztott tulajdonság használatával azonos rekordot kell azonosítani a különböző gyűjteményekben, például egy névvel lekérni egy azonosítót az egyik listából, és egy e-mailt egy másikból. Az első listán végigiterálva lassú megtalálni az egyező rekordot a második gyűjteményben. Különösen a második gyűjtemény ismételt szűrése nagy többletterheléssel jár.

Két gyűjtemény, az egyik azonosítóval és Név, a másik Név és e-mail cím:

$Employees = 1..10000 | ForEach-Object {
    [pscustomobject]@{
        Id   = $_
        Name = "Name$_"
    }
}

$Accounts = 2500..7500 | ForEach-Object {
    [pscustomobject]@{
        Name  = "Name$_"
        Email = "Name$_@fabrikam.com"
    }
}

A gyűjtemények egyeztetésének szokásos módja, hogy egy objektumlistát adunk vissza a következő tulajdonságokkal: azonosító, névés e-mail.

$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
    }
}

Ennek a megvalósításnak azonban a $Accounts gyűjteményben lévő 5000 elemet egyszer kell szűrnie a $Employee gyűjtemény minden elemére. Ez akár perceket is igénybe vehet, még ehhez az egyértékű kereséshez is.

Ehelyett létrehozhat egy kivonattáblát, amely kulcsként a megosztott Name tulajdonságot, az egyező fiókot pedig értékként használja.

$LookupHash = @{}
foreach ($Account in $Accounts) {
    $LookupHash[$Account.Name] = $Account
}

A kulcsok keresése egy kivonattáblában sokkal gyorsabb, mint a gyűjtemények tulajdonságértékek szerinti szűrése. A gyűjtemény minden elemének ellenőrzése helyett a PowerShell ellenőrizheti, hogy a kulcs definiálva van-e, és használja-e az értékét.

$Results = $Employees | ForEach-Object -Process {
    $Email = $LookupHash[$_.Name].Email
    [pscustomobject]@{
        Id    = $_.Id
        Name  = $_.Name
        Email = $Email
    }
}

Ez sokkal gyorsabb. Amíg a ciklusszűrő végrehajtása percekig tartott, a kivonatkeresés kevesebb mint egy másodpercet vesz igénybe.

Gondosan használja Write-Host

A Write-Host parancs csak akkor használható, ha formázott szöveget kell írnia a gazdakonzolra, nem pedig objektumokat a Success folyamatba.

Write-Host nagyságrenddel lassabb lehet, mint [Console]::WriteLine() bizonyos gazdagépek esetében, például pwsh.exe, powershell.exevagy powershell_ise.exe. A [Console]::WriteLine() azonban nem garantáltan minden gazdagépen működik. Emellett a [Console]::WriteLine() használatával írt kimenet nem a Start-Transcriptáltal indított átiratokra lesz megírva.

igény szerinti fordítás

A PowerShell lefordítja a szkriptkódot az értelmezett bájtkódra. A PowerShell 3-tól kezdődően a ciklusban ismétlődően végrehajtott kód esetében a PowerShell képes javítani a teljesítményt azzal, hogy Just-in-time (JIT) fordítással a kódot natív kóddá alakítja.

A 300-nál kevesebb utasítást tartalmazó hurkok alkalmasak a JIT-fordítás végrehajtására. Az ennél nagyobb hurkok túl költségesek az összeállításhoz. Amikor a ciklust 16 alkalommal hajtják végre, a szkript JIT-fordítás alá kerül a háttérben. Amikor a JIT-fordítás befejeződik, a végrehajtás átkerül a lefordított kódba.

Kerüljük a függvény ismételt hívását

Egy függvény meghívása költséges művelet lehet. Ha egy sűrű, sokszor ismétlődő ciklusban hív meg egy függvényt, fontolja meg, hogy a ciklust a függvénybe helyezi.

Vegye figyelembe a következő példákat:

$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'
        }
    }
}

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

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

Kerülje a parancsmag-folyamatok körbefuttatását

A legtöbb parancsmag a pipeline-hoz van implementálva, amely egy szekvenciális szintaxis és folyamat. Például:

cmdlet1 | cmdlet2 | cmdlet3

Az új folyamatok inicializálása költséges lehet, ezért kerülje a parancsmagfolyamatok egy másik meglévő folyamatba való burkolását.

Vegye figyelembe az alábbi példát. A Input.csv fájl 2100 sort tartalmaz. A Export-Csv parancs be van ágyazva a ForEach-Object folyamatba. A Export-Csv parancsmag a ForEach-Object ciklus minden iterációjára meghívódik.

$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

A következő példában a Export-Csv parancs a ForEach-Object folyamaton kívülre került. Ebben az esetben a Export-Csv csak egyszer lesz meghívva, de továbbra is feldolgozza a ForEach-Objectátadott összes objektumot.

$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

A le nem írt példa 372-szer gyorsabb. Figyelje meg azt is, hogy az első implementációhoz szükség van a Append paraméterre, amely a későbbi implementációhoz nem szükséges.

A szükségtelen gyűjtemények számbavételének elkerülése

A PowerShell-összehasonlító operátorok konvergens funkcióval rendelkeznek a gyűjtemények összehasonlítása során. Ha a kifejezés bal oldali értéke gyűjtemény, az operátor a gyűjtemény azon elemeit adja vissza, amelyek megfelelnek a kifejezés jobb oldali értékének.

Ez a funkció egyszerű módot kínál a gyűjtemények szűrésére. Például:

PS> $Collection = 1..99
PS> ($Collection -like '*1*') -join ' '

1 10 11 12 13 14 15 16 17 18 19 21 31 41 51 61 71 81 91

Ha azonban olyan feltételes utasításban használ gyűjtemény-összehasonlítást, amely csak logikai eredményt vár el, ez a funkció gyenge teljesítményt eredményezhet.

Vegyük például a következőt:

if ($Collection -like '*1*') { 'Found' }

Ebben a példában a PowerShell összehasonlítja a jobb oldali értéket a gyűjtemény minden értékével, és eredménygyűjteményt ad vissza. Mivel az eredmény nem üres, a nem null értékű eredmény a következőképpen lesz kiértékelve $true. A feltétel igaz az első egyezés megtalálásakor, de a PowerShell továbbra is számbavételt alkalmaz a teljes gyűjteményen. Ez a számbavétel jelentős teljesítménybeli hatással lehet a nagy gyűjteményekre.

A teljesítmény javításának egyik módja a Where() gyűjtemény metódusának használata. A Where() metódus leállítja a gyűjtemény kiértékelését, miután megtalálta az első egyezést.

# Create an array of 1048576 items
$Collection = foreach ($x in 1..1MB) { $x }
(Measure-Command { if ($Collection -like '*1*') { 'Found' } }).TotalMilliseconds
633.3695
(Measure-Command { if ($Collection.Where({ $_ -like '*1*' }, 'first')) { 'Found' } }).TotalMilliseconds
2.607

Egymillió elem esetén a Where() módszer használata jelentősen gyorsabb.

Objektum létrehozása

Az objektumok New-Object parancsmaggal való létrehozása lassú lehet. Az alábbi kód összehasonlítja az objektumok New-Object parancsmaggal való létrehozásának teljesítményét a [pscustomobject] típusú gyorsítóval.

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

A PowerShell 5.0 hozzáadta a new() statikus metódust az összes .NET-típushoz. Az alábbi kód összehasonlítja az objektumok New-Object parancsmaggal való létrehozásának teljesítményét a new() metódussal.

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

Új objektumok dinamikus létrehozása az OrderedDictionary használatával

Előfordulhatnak olyan helyzetek, amikor valamilyen bemenet alapján dinamikusan létre kell hoznunk az objektumokat, ez lehet a leggyakrabban használt módszer egy új PSObject létrehozására, majd új tulajdonságok hozzáadására a Add-Member parancsmag használatával. Az ezzel a technikával készült kis gyűjtemények teljesítményköltsége elhanyagolható lehet, azonban a nagy gyűjtemények esetében nagyon észrevehetővé válhat. Ebben az esetben az ajánlott módszer egy [OrderedDictionary] használata, majd PSObject konvertálása a [pscustomobject] típusú gyorsítóval. További információ: Rendezett szótárak létrehozásaabout_Hash_Tablesszakasza.

Tegyük fel, hogy a következő API-választ tárolja a $jsonváltozó.

{
  "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" ]
      ]
    }
  ]
}

Tegyük fel, hogy ezeket az adatokat egy CSV-be szeretné exportálni. Először létre kell hoznia új objektumokat, és hozzá kell adnia a tulajdonságokat és az értékeket a Add-Member parancsmag használatával.

$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
}

Egy OrderedDictionaryhasználatával a kód a következőre fordítható le:

$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
}

Mindkét esetben a $result kimenete megegyezik:

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

Az utóbbi megközelítés exponenciálisan hatékonyabbá válik az objektumok és a tagtulajdonságok számának növekedésével.

Íme egy 5 tulajdonsággal rendelkező objektumok létrehozásának három módszerének teljesítmény-összehasonlítása:

$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'
        }
    }
}

És ezek az eredmények:

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