Considerazioni sulle prestazioni di scripting di PowerShell
Gli script di PowerShell che sfruttano direttamente .NET ed evitano che la pipeline sia più veloce di PowerShell idiotica. PowerShell idiomatico usa cmdlet e funzioni di PowerShell, spesso sfruttando la pipeline e usando .NET solo quando necessario.
Nota
Molte delle tecniche descritte di seguito non sono idiotiche di PowerShell e possono ridurre la leggibilità di uno script di PowerShell. Gli autori di script sono invitati a usare PowerShell idiomatico, a meno che le prestazioni non determinino diversamente.
Eliminazione dell'output
Esistono molti modi per evitare di scrivere oggetti nella pipeline.
- Assegnazione o reindirizzamento di file a
$null
- Cast a
[void]
- Pipe a
Out-Null
La velocità di assegnazione a $null
, il cast a [void]
e il reindirizzamento dei file a $null
sono quasi identici. Tuttavia, la chiamata Out-Null
in un ciclo di grandi dimensioni può essere notevolmente più lenta, soprattutto 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'
}
}
}
Questi test sono stati eseguiti in un computer Windows 11 in PowerShell 7.3.4. Di seguito sono riportati i risultati:
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
I tempi e le velocità relative possono variare a seconda dell'hardware, della versione di PowerShell e del carico di lavoro corrente nel sistema.
Aggiunta di matrici
La generazione di un elenco di elementi viene spesso eseguita usando una matrice con l'operatore di addizione:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
L'aggiunta di matrici non è efficiente perché le matrici hanno una dimensione fissa. Ogni aggiunta alla matrice crea una nuova matrice sufficientemente grande da contenere tutti gli elementi degli operandi sinistro e destro. Gli elementi di entrambi gli operandi vengono copiati nella nuova matrice. Per le raccolte di piccole dimensioni, questo sovraccarico potrebbe non essere importante. Le prestazioni possono risentire delle raccolte di grandi dimensioni.
Ci sono un paio di alternative. Se in realtà non è necessaria una matrice, prendere in considerazione l'uso di un elenco generico tipizzato ([List<T>]
):
$results = [System.Collections.Generic.List[object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
L'impatto sulle prestazioni dell'uso dell'aggiunta della matrice aumenta in modo esponenziale con le dimensioni della raccolta e le aggiunte di numeri. Questo codice confronta in modo esplicito l'assegnazione di valori a una matrice con l'aggiunta di matrici e l'uso del Add(T)
metodo su un [List<T>]
oggetto . Definisce l'assegnazione esplicita come baseline per le prestazioni.
$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'
}
}
}
Questi test sono stati eseguiti in un computer Windows 11 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
Quando si lavora con raccolte di grandi dimensioni, l'aggiunta di matrici è notevolmente più lenta rispetto all'aggiunta a un oggetto List<T>
.
Quando si usa un [List<T>]
oggetto , è necessario creare l'elenco con un tipo specifico, ad esempio [String]
o [Int]
. Quando si aggiungono oggetti di un tipo diverso all'elenco, viene eseguito il cast al tipo specificato. Se non è possibile eseguire il cast al tipo specificato, il metodo genera un'eccezione.
$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
Quando è necessario che l'elenco sia una raccolta di diversi tipi di oggetti, crearlo con [Object]
come tipo di elenco. È possibile enumerare l'insieme per esaminare i tipi degli oggetti in esso contenuti.
$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
Se è necessaria una matrice, è possibile chiamare il ToArray()
metodo nell'elenco oppure consentire a PowerShell di creare automaticamente la matrice:
$results = @(
Get-Something
Get-SomethingElse
)
In questo esempio PowerShell crea un oggetto [ArrayList]
per contenere i risultati scritti nella pipeline all'interno dell'espressione di matrice. Poco prima di assegnare a $results
, PowerShell converte l'oggetto [ArrayList]
in un oggetto [Object[]]
.
Addizione di stringhe
Le stringhe non sono modificabili. Ogni aggiunta alla stringa crea effettivamente una nuova stringa sufficientemente grande da contenere il contenuto degli operandi sinistro e destro, quindi copia gli elementi di entrambi gli operandi nella nuova stringa. Per le stringhe di piccole dimensioni, questo sovraccarico potrebbe non essere importante. Per stringhe di grandi dimensioni, questo può influire sull'utilizzo di prestazioni e memoria.
Esistono almeno due alternative:
- L'operatore
-join
concatena le stringhe - La classe .NET
[StringBuilder]
fornisce una stringa modificabile
Nell'esempio seguente vengono confrontate le prestazioni di questi tre metodi di compilazione di una stringa.
$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'
}
}
}
Questi test sono stati eseguiti in un computer Windows 11 in PowerShell 7.4.2. L'output mostra che l'operatore -join
è il più veloce, seguito dalla [StringBuilder]
classe .
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
I tempi e le velocità relative possono variare a seconda dell'hardware, della versione di PowerShell e del carico di lavoro corrente nel sistema.
Elaborazione di file di grandi dimensioni
Il modo idiotico per elaborare un file in PowerShell potrebbe essere simile al seguente:
Get-Content $path | Where-Object Length -GT 10
Questo può essere un ordine di grandezza più lento rispetto all'uso diretto delle API .NET. Ad esempio, è possibile usare la classe .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()
}
}
È anche possibile usare il metodo di , che esegue il ReadLines
wrapping StreamReader
di , semplifica il processo di [System.IO.File]
lettura:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Ricerca di voci per proprietà in raccolte di grandi dimensioni
È comune usare una proprietà condivisa per identificare lo stesso record in raccolte diverse, ad esempio usando un nome per recuperare un ID da un elenco e un messaggio di posta elettronica da un altro. L'iterazione del primo elenco per trovare il record corrispondente nella seconda raccolta è lento. In particolare, il filtro ripetuto della seconda raccolta presenta un sovraccarico elevato.
Date due raccolte, una con ID e Name, l'altra con Name e Email:
$Employees = 1..10000 | ForEach-Object {
[PSCustomObject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[PSCustomObject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
Il modo consueto per riconciliare queste raccolte per restituire un elenco di oggetti con le proprietà ID, Name e Email potrebbe essere simile al seguente:
$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
}
}
Tuttavia, tale implementazione deve filtrare tutti i 5000 elementi nella $Accounts
raccolta una volta per ogni elemento della $Employee
raccolta. Questa operazione può richiedere minuti, anche per questa ricerca a valore singolo.
È invece possibile creare una tabella hash che usa la proprietà Nome condiviso come chiave e l'account corrispondente come valore.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
La ricerca di chiavi in una tabella hash è molto più veloce rispetto al filtro di una raccolta in base ai valori delle proprietà. Anziché controllare ogni elemento nella raccolta, PowerShell può controllare se la chiave è definita e usarne il valore.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Questo è molto più veloce. Durante il completamento del filtro di ciclo sono necessari minuti, la ricerca hash richiede meno di un secondo.
Usare con attenzione Write-Host
Il Write-Host
comando deve essere usato solo quando è necessario scrivere testo formattato nella console host, anziché scrivere oggetti nella pipeline Success .
Write-Host
può essere un ordine di grandezza più lento rispetto [Console]::WriteLine()
a per host specifici, ad pwsh.exe
esempio , powershell.exe
o powershell_ise.exe
. Tuttavia, [Console]::WriteLine()
non è garantito il funzionamento in tutti gli host. Inoltre, l'output scritto con [Console]::WriteLine()
non viene scritto nelle trascrizioni avviate da Start-Transcript
.
Compilazione JIT
PowerShell compila il codice script in bytecode interpretato. A partire da PowerShell 3, per il codice eseguito ripetutamente in un ciclo, PowerShell può migliorare le prestazioni compilando il codice in codice nativo.
I cicli con meno di 300 istruzioni sono idonei per la compilazione JIT. Cicli più grandi di quelli troppo costosi per la compilazione. Quando il ciclo è stato eseguito 16 volte, lo script viene compilato in background. Al termine della compilazione JIT, l'esecuzione viene trasferita al codice compilato.
Evitare chiamate ripetute a una funzione
La chiamata a una funzione può essere un'operazione costosa. Se si chiama una funzione in un ciclo stretto a esecuzione prolungata, è consigliabile spostare il ciclo all'interno della funzione.
Vedi gli esempi seguenti:
$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'
}
}
}
L'esempio di ciclo for basic è la linea di base per le prestazioni. Il secondo esempio esegue il wrapping del generatore di numeri casuali in una funzione chiamata in un ciclo stretto. Il terzo esempio sposta il ciclo all'interno della funzione. La funzione viene chiamata una sola volta, ma il codice genera comunque la stessa quantità di numeri casuali. Si noti la differenza nei tempi di esecuzione per ogni esempio.
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
Evitare di eseguire il wrapping delle pipeline dei cmdlet
La maggior parte dei cmdlet viene implementata per la pipeline, ovvero una sintassi e un processo sequenziali. Ad esempio:
cmdlet1 | cmdlet2 | cmdlet3
L'inizializzazione di una nuova pipeline può risultare costosa, pertanto è consigliabile evitare di eseguire il wrapping di una pipeline di cmdlet in un'altra pipeline esistente.
Si consideri l'esempio seguente. Il Input.csv
file contiene 2100 righe. Il Export-Csv
comando viene sottoposto a wrapping all'interno della ForEach-Object
pipeline. Il Export-Csv
cmdlet viene richiamato per ogni iterazione del ForEach-Object
ciclo.
$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
Per l'esempio successivo, il Export-Csv
comando è stato spostato all'esterno della ForEach-Object
pipeline.
In questo caso, Export-Csv
viene richiamato una sola volta, ma elabora comunque tutti gli oggetti passati da 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
L'esempio non sottoposto a wrapping è 372 volte più veloce. Si noti anche che la prima implementazione richiede il parametro Append , che non è necessario per l'implementazione successiva.
Usare OrderedDictionary per creare dinamicamente nuovi oggetti
In alcune situazioni potrebbe essere necessario creare oggetti in modo dinamico in base ad alcuni input, il modo forse più comunemente usato per creare un nuovo PSObject e quindi aggiungere nuove proprietà usando il Add-Member
cmdlet . Il costo delle prestazioni per le raccolte di piccole dimensioni che usano questa tecnica può essere trascurabile, ma può diventare molto evidente per le raccolte di grandi dimensioni. In tal caso, l'approccio consigliato consiste nell'usare un e [OrderedDictionary]
quindi convertirlo in un PSObject usando l'acceleratore [pscustomobject]
di tipi. Per altre informazioni, vedere la sezione Creazione di dizionari ordinati di about_Hash_Tables.
Si supponga di avere la risposta API seguente archiviata nella variabile $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" ]
]
}
]
}
Si supponga ora di voler esportare questi dati in un file CSV. Prima di tutto è necessario creare nuovi oggetti e aggiungere le proprietà e i valori usando il Add-Member
cmdlet .
$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
}
Usando un OrderedDictionary
oggetto , il codice può essere convertito in:
$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 entrambi i casi l'output $result
sarà lo stesso:
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
Quest'ultimo approccio diventa esponenzialmente più efficiente man mano che aumenta il numero di oggetti e proprietà membro.
Ecco un confronto delle prestazioni di tre tecniche per la creazione di oggetti con 5 proprietà:
$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'
}
}
}
E questi sono i risultati:
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