Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
PowerShell-skript som utnyttjar .NET direkt och undviker pipelinen tenderar att vara snabbare än idiomatiska PowerShell. Idiomatic PowerShell använder cmdletar och PowerShell-funktioner, använder ofta pipelinen och använder endast .NET när det behövs.
Anteckning
Många av de tekniker som beskrivs här är inte idiomatiska PowerShell och kan minska läsbarheten för ett PowerShell-skript. Skriptförfattare rekommenderas att använda idiomatisk PowerShell om inte prestandan kräver något annat.
Undertrycka utdata
Det finns många sätt att undvika att skriva objekt till pipelinen.
- Tilldelning eller filomdirigering till
$null
- Sända till
[void]
- Rör till
Out-Null
Hastigheterna för att tilldela till $null
, gjutning till [void]
och filomdirigering till $null
är nästan identiska. Det kan dock vara betydligt långsammare att anropa Out-Null
i en stor loop, särskilt i 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'
}
}
}
Dessa tester kördes på en Windows 11-dator i PowerShell 7.3.4. Resultaten visas nedan:
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
Tiderna och de relativa hastigheterna kan variera beroende på maskinvara, version av PowerShell och den aktuella arbetsbelastningen i systemet.
Array-tillägg
Det går ofta att generera en lista över objekt med hjälp av en matris med additionsoperatorn:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Anteckning
I PowerShell 7.5 optimerades matristillägget och skapar inte längre en ny matris för varje åtgärd. De prestandaöverväganden som beskrivs här gäller fortfarande för PowerShell-versioner före 7.5. Mer information finns i Nyheter i PowerShell 7.5.
Matristillägget är ineffektivt eftersom matriser har en fast storlek. Varje tillägg till matrisen skapar en ny matris som är tillräckligt stor för att innehålla alla element i både vänster och höger operander. Elementen i båda operanderna kopieras till den nya matrisen. För små samlingar kanske den här kostnaden inte spelar någon roll. Prestanda kan bli lidande för stora samlingar.
Det finns ett par alternativ. Om du faktiskt inte behöver en matris bör du i stället överväga att använda en typad allmän lista ([List<T>]
):
$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
Prestandapåverkan av att använda arraytillägg växer exponentiellt med storleken på samlingen och antalet tillägg. Den här koden jämför explicit tilldelning av värden till en matris med att använda matristillägg och använda metoden Add(T)
på ett [List<T>]
objekt. Den definierar explicit tilldelning som baslinje för prestanda.
$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'
}
}
}
Dessa tester kördes på en Windows 11-dator i 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
När du arbetar med stora samlingar är det betydligt långsammare att lägga till i en array än att lägga till i en List<T>
.
När du använder ett [List<T>]
objekt måste du skapa listan med en viss typ, till exempel [string]
eller [int]
. När du lägger till objekt av en annan typ i listan omvandlas de till den angivna typen. Om de inte kan omvandlas till den angivna typen genererar metoden ett undantag.
$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
När du behöver listan som en samling med olika typer av objekt skapar du den med [Object]
som listtyp. Du kan räkna upp samlingen genom att granska typerna av objekten i den.
$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
Om du behöver en matris kan du anropa metoden ToArray()
i listan eller låta PowerShell skapa matrisen åt dig:
$results = @(
Get-Something
Get-SomethingElse
)
I det här exemplet skapar PowerShell en [ArrayList]
för att lagra resultaten som skrivits till pipelinen i matrisuttrycket. Precis innan du tilldelar till $results
konverterar PowerShell [ArrayList]
till en [Object[]]
.
Strängtillägg
Strängar är oföränderliga. Varje tillägg till strängen skapar faktiskt en ny sträng som är tillräckligt stor för att innehålla innehållet i både vänster och höger operander och kopierar sedan elementen i båda operanderna till den nya strängen. För små strängar kanske den här kostnaden inte spelar någon roll. För stora strängar kan detta påverka prestanda och minnesförbrukning.
Det finns minst två alternativ:
- Operatorn
-join
sammanfogar strängar - Klassen .NET
[StringBuilder]
innehåller en föränderlig sträng
I följande exempel jämförs prestandan för dessa tre metoder för att skapa en sträng.
$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'
}
}
}
Dessa tester kördes på en Windows 11-dator i PowerShell 7.4.2. Utdata visar att operatorn -join
är snabbast följt av klassen [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
Tiderna och de relativa hastigheterna kan variera beroende på maskinvara, version av PowerShell och den aktuella arbetsbelastningen i systemet.
Bearbeta stora filer
Det idiomatiska sättet att bearbeta en fil i PowerShell kan se ut ungefär så här:
Get-Content $path | Where-Object Length -GT 10
Det kan vara en tiopotens långsammare än att använda .NET-API:er direkt. Du kan till exempel använda klassen .NET [StreamReader]
:
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()
}
}
Du kan också använda metoden ReadLines
för [System.IO.File]
, som omsluter StreamReader
, förenklar läsprocessen:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Söka efter poster efter egenskap i stora samlingar
Det är vanligt att behöva använda en delad egenskap för att identifiera samma post i olika samlingar, som att använda ett namn för att hämta ett ID från en lista och ett e-postmeddelande från en annan. Att iterera över den första listan för att hitta den matchande posten i den andra samlingen är långsamt. I synnerhet har den upprepade filtreringen av den andra samlingen stora omkostnader.
Med två samlingar, en med ett -ID och Namn, den andra med Namn och E-post:
$Employees = 1..10000 | ForEach-Object {
[pscustomobject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[pscustomobject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
Det vanliga sättet att stämma av dessa samlingar för att returnera en lista över objekt med egenskaperna ID, Nameoch Email kan se ut så här:
$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
}
}
Implementeringen måste dock filtrera alla 5 000 objekt i $Accounts
samling en gång för varje objekt i $Employee
-samlingen. Det kan ta minuter, även för denna enkelvärdesuppslagning.
I stället kan du skapa en Hash-tabell som använder den delade egenskapen Name som en nyckel och det matchande kontot som värde.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Det går mycket snabbare att leta upp nycklar i en hash-tabell än att filtrera en samling efter egenskapsvärden. I stället för att kontrollera varje objekt i samlingen kan PowerShell kontrollera om nyckeln har definierats och använda dess värde.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Det här går mycket snabbare. Loopningsfiltret tog några minuter att slutföra, men hash-sökningen tar mindre än en sekund.
Använd Write-Host noggrant
Kommandot Write-Host
bör endast användas när du behöver skriva formaterad text till värdkonsolen i stället för att skriva objekt till pipelinen Lyckades.
Write-Host
kan vara en storleksordning som är långsammare än [Console]::WriteLine()
för specifika värdar som pwsh.exe
, powershell.exe
eller powershell_ise.exe
. Men [Console]::WriteLine()
är inte garanterat att fungera på alla värdar. Utdata som skrivs med [Console]::WriteLine()
skrivs inte heller till avskrifter som startas av Start-Transcript
.
JIT-kompilering
PowerShell kompilerar skriptkoden till bytekod som tolkas. Från och med PowerShell 3, för kod som körs upprepade gånger i en loop, kan PowerShell förbättra prestanda genom att JIT (Just-in-time) kompilerar koden till intern kod.
Loopar som har färre än 300 instruktioner är berättigade till JIT-kompilering. Loopar som är större än så är för kostsamma för att kompileras. När loopen har körts 16 gånger är skriptet JIT-kompilerat i bakgrunden. När JIT-kompilering är klar överförs körningen till den kompilerade koden.
Undvik upprepade anrop till en funktion
Att anropa en funktion kan vara en dyr åtgärd. Om du anropar en funktion i en långkörande snäv loop, kan du överväga att flytta loopen inuti funktionen.
Tänk på följande exempel:
$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'
}
}
}
Exemplet Basic for-loop är baslinjen för prestanda. Det andra exemplet omsluter slumptalsgeneratorn i en funktion som anropas i en snäv loop. Det tredje exemplet flyttar loopen inuti funktionen. Funktionen anropas bara en gång, men koden genererar fortfarande samma mängd slumpmässiga tal. Observera skillnaden i exekveringstider för varje exempel.
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
Undvik att omsluta cmdlet-pipelines
De flesta cmdletar implementeras för pipelinen, vilket är en sekventiell syntax och process. Till exempel:
cmdlet1 | cmdlet2 | cmdlet3
Det kan vara dyrt att initiera en ny pipeline. Därför bör du undvika att omsluta en cmdlet-pipeline till en annan befintlig pipeline.
Tänk dig följande exempel. Filen Input.csv
innehåller 2 100 rader. Kommandot Export-Csv
är inneslutet i den ForEach-Object
-pipeline. Cmdleten Export-Csv
anropas för varje iteration av ForEach-Object
-loopen.
$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
I nästa exempel flyttades kommandot Export-Csv
utanför ForEach-Object
pipeline.
I det här fallet anropas Export-Csv
bara en gång, men bearbetar fortfarande alla objekt som skickas ut från 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
Det oöppnade exemplet är 372 gånger snabbare. Observera också att den första implementeringen kräver parametern Lägg till, vilket inte krävs för den senare implementeringen.
Skapa objekt
Det kan ta lång tid att skapa objekt med hjälp av cmdleten New-Object
. Följande kod jämför prestandan för att skapa objekt med hjälp av cmdleten New-Object
med [pscustomobject]
typaccelerator.
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 har lagt till den statiska metoden new()
för alla .NET-typer. Följande kod jämför prestandan för att skapa objekt med hjälp av cmdleten New-Object
med metoden new()
.
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
Använd OrderedDictionary för att dynamiskt skapa nya objekt
Det finns situationer där vi kan behöva skapa objekt dynamiskt baserat på vissa indata, det kanske vanligaste sättet att skapa en ny PSObject- och sedan lägga till nya egenskaper med hjälp av cmdleten Add-Member
. Prestandakostnaden för små samlingar med den här tekniken kan vara försumbar, men den kan bli mycket märkbar för stora samlingar. I så fall är den rekommenderade metoden att använda en [OrderedDictionary]
och sedan konvertera den till en PSObject- med hjälp av [pscustomobject]
typaccelerator. Mer information finns i avsnittet Skapa ordnade ordlistor i about_Hash_Tables.
Anta att du har följande API-svar lagrat i variabeln $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" ]
]
}
]
}
Anta nu att du vill exportera dessa data till en CSV. Först måste du skapa nya objekt och lägga till egenskaper och värden med hjälp av cmdleten 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
}
Med hjälp av en OrderedDictionary
kan koden översättas till:
$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
}
I båda fallen skulle $result
utdata vara desamma:
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
Den senare metoden blir exponentiellt effektivare när antalet objekt och medlemsegenskaper ökar.
Här är en prestandajämförelse av tre tekniker för att skapa objekt med 5 egenskaper:
$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'
}
}
}
Och det här är resultatet:
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