Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
PowerShell-scripts die rechtstreeks gebruikmaken van .NET en voorkomen dat de pijplijn sneller is dan idiomatische PowerShell. Idiomatic PowerShell maakt gebruik van cmdlets en PowerShell-functies, vaak gebruikmakend van de pijplijn en gebruiken van .NET alleen wanneer dat nodig is.
Notitie
Veel van de hier beschreven technieken zijn geen idiomatische PowerShell en kunnen de leesbaarheid van een PowerShell-script verminderen. Auteurs van scripts wordt aangeraden om idiomatische PowerShell te gebruiken, tenzij de prestaties anders dicteren.
Uitvoer onderdrukken
Er zijn veel manieren om te voorkomen dat objecten naar de pijplijn worden geschreven.
- Toewijzing en bestandsoverdracht naar
$null - Afspelen naar
[void] - Pijp naar
Out-Null
De snelheden van het toewijzen aan $null, casten naar [void]en bestandsomleiding naar $null zijn bijna identiek. Het aanroepen van Out-Null in een grote lus kan echter aanzienlijk langzamer zijn, met name 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'
}
}
}
Deze tests werden uitgevoerd op een Windows 11-computer in PowerShell 7.3.4. De resultaten worden hieronder weergegeven:
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
De tijden en relatieve snelheden kunnen variëren, afhankelijk van de hardware, de versie van PowerShell en de huidige werkbelasting op het systeem.
Matrix toevoegen
Het genereren van een lijst met items wordt vaak gedaan met behulp van een matrix met de optellenoperator:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Notitie
In PowerShell 7.5 is matrixopvoeging geoptimaliseerd en wordt voor elke bewerking geen nieuwe matrix meer gemaakt. De hier beschreven prestatieoverwegingen zijn nog steeds van toepassing op PowerShell-versies vóór 7.5. Zie Wat is er nieuw in PowerShell 7.5 voor meer informatie.
Het toevoegen van matrices is inefficiënt omdat matrices een vaste grootte hebben. Elke toevoeging aan de matrix maakt een nieuwe matrix die groot genoeg is om alle elementen van zowel de linker- als rechteroperanden vast te houden. De elementen van beide operanden worden gekopieerd naar de nieuwe array. Voor kleine verzamelingen maakt deze overhead mogelijk niet uit. Prestaties kunnen verminderd worden bij grote verzamelingen.
Er zijn een paar alternatieven. Als u geen matrix nodig hebt, kunt u in plaats daarvan een getypte algemene lijst gebruiken ([List<T>]):
$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
De prestatie-impact van het gebruik van array toevoegingen neemt exponentieel toe met de grootte van de verzameling en het aantal toevoegingen. Deze code vergelijkt expliciet het toewijzen van waarden aan een matrix met het toevoegen van matrices en het gebruik van de methode Add(T) op een [List<T>]-object. Het definieert expliciete toewijzing als de basislijn voor prestaties.
$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'
}
}
}
Deze tests werden uitgevoerd op een Windows 11-computer in PowerShell 7.3.4.
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
Wanneer u met grote verzamelingen werkt, is het toevoegen van arrays veel langzamer dan het toevoegen aan een List<T>.
Wanneer u een [List<T>]-object gebruikt, moet u de lijst maken met een specifiek type, zoals [string] of [int]. Wanneer u objecten van een ander type aan de lijst toevoegt, worden ze naar het opgegeven type gecast. Als ze niet naar het opgegeven type kunnen worden gecast, genereert de methode een uitzondering.
$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
Wanneer u de lijst nodig hebt om een verzameling van verschillende typen objecten te zijn, maakt u deze met [Object] als lijsttype. U kunt de verzameling inventariseren welke typen objecten erin zijn opgenomen.
$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
Als u wel een matrix nodig hebt, kunt u de ToArray() methode in de lijst aanroepen of u kunt PowerShell de matrix voor u laten maken:
$results = @(
Get-Something
Get-SomethingElse
)
In dit voorbeeld maakt PowerShell een [ArrayList] voor het opslaan van de resultaten die naar de pijplijn in de matrixexpressie zijn geschreven. Net voordat u aan $resultstoewijst, converteert PowerShell de [ArrayList] naar een [Object[]].
Typeveilige verzamelingen
PowerShell is een losjes getypte taal, waardoor code eenvoudiger is, maar dat kan gevolgen hebben voor de prestaties. Overweeg om typeveilige verzamelingen (of typespecifieke) verzamelingen te gebruiken. Typeveilige verzamelingen verbruiken minder geheugen en zijn sneller. Vergelijk de volgende voorbeelden:
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$ListInt = [System.Collections.Generic.List[int]]::new()
for ($i = 0; $i -lt 1mb; $i++) {
$ListInt.Add($i)
}
$Stopwatch.Stop()
Write-Host "Time to add 1mb integers to List[int]: $($Stopwatch.Elapsed.TotalSeconds) seconds."
Time to add 1mb integers to List[int]: 9.8841501 seconds.
Een lijst van [int] maken is sneller dan een lijst van [Object] maken.
$Stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$ListObject = [System.Collections.Generic.List[Object]]::new()
for ($i = 0; $i -lt 1mb; $i++) {
$ListObject.Add($i)
}
$Stopwatch.Stop()
Write-Host "Time to add 1mb integers to List[Object]: $($Stopwatch.Elapsed.TotalSeconds) seconds."
Time to add 1mb integers to List[Object]: 10.5677782 seconds.
Optellen van tekenreeks
Tekenreeksen zijn onveranderbaar. Elke toevoeging aan de tekenreeks maakt daadwerkelijk een nieuwe tekenreeks die groot genoeg is om de inhoud van zowel de linker- als rechteroperanden op te slaan en kopieert vervolgens de elementen van beide operanden naar de nieuwe tekenreeks. Voor kleine tekenreeksen maakt deze overhead mogelijk niet uit. Voor grote tekenreeksen kan dit van invloed zijn op de prestaties en het geheugenverbruik.
Er zijn ten minste twee alternatieven:
- De operator
-jointekenreeksen samenvoegen - De klasse .NET
[StringBuilder]biedt een onveranderbare tekenreeks
Hieronder wordt de prestatie van de drie methoden om een tekenreeks te bouwen vergeleken.
$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'
}
}
}
Deze tests zijn uitgevoerd op een Windows 11-computer in PowerShell 7.4.2. In de uitvoer ziet u dat de operator -join de snelste is, gevolgd door de klasse [StringBuilder].
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
De tijden en relatieve snelheden kunnen variëren, afhankelijk van de hardware, de versie van PowerShell en de huidige werkbelasting op het systeem.
Grote bestanden verwerken
De idiomatische manier om een bestand te verwerken in PowerShell kan er ongeveer als volgt uitzien:
Get-Content $path | Where-Object Length -GT 10
Dit kan een orde van grootte langzamer zijn dan het rechtstreeks gebruiken van .NET-API's. U kunt bijvoorbeeld de klasse .NET [StreamReader] gebruiken:
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()
}
}
U kunt ook de ReadLines methode van [System.IO.File]gebruiken, die StreamReaderverpakt, vereenvoudigt het leesproces:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Items op basis van eigenschap opzoeken in grote verzamelingen
Het is gebruikelijk om een gedeelde eigenschap te gebruiken om dezelfde record in verschillende verzamelingen te identificeren, zoals het gebruik van een naam om een id op te halen uit de ene lijst en een e-mailbericht van een andere. Het herhalen van de eerste lijst om de overeenkomende record in de tweede verzameling te vinden, is traag. Met name de herhaalde filtering van de tweede verzameling heeft een grote overhead.
Gegeven twee verzamelingen, een met een id en naam, de andere met naam en e-mail:
$Employees = 1..10000 | ForEach-Object {
[pscustomobject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[pscustomobject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
De gebruikelijke manier om deze verzamelingen af te stemmen om een lijst met objecten te retourneren met de -Id, -Naamen -e-maileigenschappen kan er als volgt uitzien:
$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
}
}
Deze implementatie moet echter alle 5000 items in de $Accounts verzameling één keer filteren voor elk item in de $Employee verzameling. Dat kan minuten duren, zelfs voor deze zoekactie met één waarde.
In plaats daarvan kunt u een Hash-tabel maken die gebruikmaakt van de eigenschap gedeelde Naam als sleutel en het overeenkomende account als waarde.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Het opzoeken van sleutels in een hash-tabel is veel sneller dan het filteren van een verzameling op eigenschapswaarden. In plaats van elk item in de verzameling te controleren, kan PowerShell controleren of de sleutel is gedefinieerd en de waarde ervan gebruiken.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Dit is veel sneller. Hoewel het lusfilter enkele minuten nodig had om te voltooien, duurt de hash-zoekactie minder dan een seconde.
Gebruik Write-Host zorgvuldig
De opdracht Write-Host mag alleen worden gebruikt wanneer u opgemaakte tekst naar de hostconsole moet schrijven in plaats van objecten naar de pijplijn Geslaagd te schrijven.
Write-Host kan een orde van grootte langzamer zijn dan [Console]::WriteLine() voor specifieke hosts, zoals pwsh.exe, powershell.exeof powershell_ise.exe.
[Console]::WriteLine() werkt echter niet gegarandeerd op alle hosts. Transcripties die zijn gestart door [Console]::WriteLine() ontvangen ook geen uitvoer die is geschreven met behulp van Start-Transcript.
JIT-compilatie
PowerShell compileert de scriptcode naar bytecode die wordt geïnterpreteerd. Vanaf PowerShell 3 kan PowerShell voor code die herhaaldelijk in een lus wordt uitgevoerd, de prestaties verbeteren door Just-In-Time (JIT) de code te compileren in systeemeigen code.
Lussen met minder dan 300 instructies komen in aanmerking voor JIT-compilatie. Lussen die groter zijn dan dat, zijn te kostbaar om te compileren. Wanneer de lus 16 keer is uitgevoerd, wordt het script op de achtergrond gecompileerd met JIT. Wanneer de JIT-compilatie is voltooid, wordt de uitvoering overgebracht naar de gecompileerde code.
Vermijd herhaalde aanroepen naar een functie
Het aanroepen van een functie kan een dure bewerking zijn. Als u een functie aanroept in een langdurige strakke lus, kunt u overwegen de lus binnen de functie te verplaatsen.
Bekijk de volgende voorbeelden:
$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'
}
}
}
Het voorbeeld Basic for-loop is de basislijn voor prestaties. In het tweede voorbeeld wordt de generator voor willekeurige getallen verpakt in een functie die in een strakke lus wordt aangeroepen. In het derde voorbeeld wordt de lus binnen de functie verplaatst. De functie wordt slechts eenmaal aangeroepen, maar de code genereert nog steeds dezelfde hoeveelheid willekeurige getallen. Let op het verschil in uitvoeringstijden voor elk voorbeeld.
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
Het vermijden van omslaan van cmdlet-pijpleidingen
De meeste cmdlets worden geïmplementeerd voor de pijplijn. Dit is een opeenvolgende syntaxis en een proces. Bijvoorbeeld:
cmdlet1 | cmdlet2 | cmdlet3
Het initialiseren van een nieuwe pijplijn kan duur zijn, daarom moet u voorkomen dat u een cmdlet-pijplijn in een andere bestaande pijplijn verpakt.
Bekijk het volgende voorbeeld. Het bestand Input.csv bevat 2100 regels. De opdracht Export-Csv wordt verpakt in de ForEach-Object-pijplijn. De Export-Csv cmdlet wordt aangeroepen voor elke iteratie van de ForEach-Object lus.
$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
In het volgende voorbeeld is de opdracht Export-Csv buiten de ForEach-Object-pijplijn verplaatst.
In dit geval wordt Export-Csv slechts één keer aangeroepen, maar worden nog steeds alle objecten verwerkt die zijn doorgegeven uit ForEach-Object.
$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
Het uitgepakte voorbeeld is 372 keer sneller. U ziet ook dat voor de eerste implementatie de parameter Append is vereist. Dit is niet vereist voor de latere implementatie.
Vermijd onnodige inventarisatie van verzamelingen
De PowerShell-vergelijkingsoperatoren hebben een functie voor het vergelijken van verzamelingen. Wanneer de linkerwaarde in de expressie een verzameling is, retourneert de operator de elementen van de verzameling die overeenkomen met de rechterwaarde van de expressie.
Deze functie biedt een eenvoudige manier om een verzameling te filteren. Bijvoorbeeld:
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
Wanneer u echter een verzamelingsvergelijking gebruikt in een voorwaardelijke instructie die alleen een booleaans resultaat verwacht, kan deze functie leiden tot slechte prestaties.
Neem bijvoorbeeld:
if ($Collection -like '*1*') { 'Found' }
In dit voorbeeld vergelijkt PowerShell de rechterwaarde met elke waarde in de verzameling en retourneert een verzameling resultaten. Omdat het resultaat niet leeg is, wordt het niet-null-resultaat geëvalueerd als $true. De voorwaarde is waar wanneer de eerste overeenkomst wordt gevonden, maar PowerShell inventariseert nog steeds de hele verzameling. Deze inventarisatie kan een aanzienlijke invloed hebben op de prestaties voor grote verzamelingen.
Een manier om de prestaties te verbeteren, is door de Where() methode van de verzameling te gebruiken. De Where() methode stopt met het evalueren van de verzameling nadat de eerste overeenkomst is gevonden.
# 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
Voor een miljoen items is het gebruik van de Where() methode aanzienlijk sneller.
Aanmaak van objecten
Het maken van objecten met behulp van de cmdlet New-Object kan traag zijn. De volgende code vergelijkt de prestaties van het maken van objecten met behulp van de cmdlet New-Object met de [pscustomobject] typeversneller.
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 heeft de new() statische methode toegevoegd voor alle .NET-typen. De volgende code vergelijkt de prestaties van het maken van objecten met behulp van de cmdlet New-Object met de 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
OrderedDictionary gebruiken om dynamisch nieuwe objecten te maken
Er zijn situaties waarin we mogelijk dynamisch objecten moeten maken op basis van bepaalde invoer, de meest gebruikte manier om een nieuwe PSObject- te maken en vervolgens nieuwe eigenschappen toe te voegen met behulp van de Add-Member-cmdlet. De prestatiekosten voor kleine verzamelingen die deze techniek gebruiken, kunnen verwaarloosbaar zijn, maar het kan zeer merkbaar worden voor grote verzamelingen. In dat geval is de aanbevolen methode om een [OrderedDictionary] te gebruiken en deze vervolgens te converteren naar een PSObject- met behulp van de [pscustomobject] typeversneller. Zie de sectie Geordende woordenlijsten maken sectie van about_Hash_Tablesvoor meer informatie.
Stel dat u het volgende API-antwoord hebt opgeslagen in de variabele $json.
{
"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" ]
]
}
]
}
Stel dat u deze gegevens wilt exporteren naar een CSV. Eerst moet u nieuwe objecten maken en de eigenschappen en waarden toevoegen met behulp van de cmdlet Add-Member.
$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
}
Met behulp van een OrderedDictionarykan de code worden vertaald naar:
$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 beide gevallen is de $result uitvoer hetzelfde:
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
De laatste benadering wordt exponentieel efficiënter naarmate het aantal objecten en lideigenschappen toeneemt.
Hier volgt een prestatievergelijking van drie technieken voor het maken van objecten met 5 eigenschappen:
$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'
}
}
}
En dit zijn de resultaten:
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