Considérations relatives aux performances des scripts PowerShell
Les scripts PowerShell qui exploitent directement .NET en évitant le pipeline ont tendance à être plus rapides que le PowerShell idiomatique. Le PowerShell idiomatique utilise des applets de commande et des fonctions PowerShell, en tirant souvent parti du pipeline et en ayant recours à .NET uniquement quand cela est nécessaire.
Notes
La plupart des techniques décrites ici ne sont pas issues du PowerShell idiomatiques, ce qui peut réduire la lisibilité d’un script PowerShell. Nous conseillons aux auteurs de scripts d’utiliser le PowerShell idiomatique, à moins que les performances ne le permettant pas.
Empêcher la génération de sortie
Il existe de nombreuses façons d’éviter d’écrire des objets dans le pipeline.
- Affectation ou redirection de fichiers vers
$null
- Cast sur
[void]
- Piping vers
Out-Null
Les vitesses d’affectation à $null
, de cast sur [void]
et de redirection de fichier vers $null
sont presque identiques. Toutefois, le fait d’appeler Out-Null
dans une grande boucle peut être beaucoup plus lent, en particulier dans 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'
}
}
}
Ces tests ont été exécutés dans PowerShell 7.3.4 sur un ordinateur Windows 11. Les résultats sont affichés ci-dessous :
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
Les temps et les vitesses relatives peuvent varier en fonction du matériel, de la version de PowerShell et de la charge de travail actuelle sur le système.
Ajout de tableaux
La génération d’une liste d’éléments se fait souvent à l’aide d’un tableau avec l’opérateur d’addition :
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
L’ajout de tableaux est inefficace, car les tableaux ont une taille fixe. Chaque ajout au tableau crée un autre tableau suffisamment grand pour contenir tous les éléments des opérandes de gauche et de droite. Les éléments des deux opérandes sont copiés dans le nouveau tableau. Pour les petits regroupements, cette surcharge peut être faible. Les performances peuvent être impactées pour les collections volumineuses.
Il existe deux alternatives à cela. Si vous n’avez pas besoin d’un tableau, vous pouvez utiliser à la place une liste générique typée ([List<T>]
) :
$results = [System.Collections.Generic.List[object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
L’impact sur les performances de l’utilisation de l’ajout de tableaux augmente de façon exponentielle selon la taille de la collection et le nombre d’ajouts. Ce code compare explicitement l’affectation de valeurs à un tableau en utilisant l’ajout à un tableau et la méthode Add(T)
sur un objet [List<T>]
. Il définit l’affectation explicite comme ligne de base pour les performances.
$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'
}
}
}
Ces tests ont été exécutés dans PowerShell 7.3.4 sur un ordinateur Windows 11.
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
Quand vous utilisez de grandes collections, l’ajout à un tableau est considérablement plus lent que l’ajout à une List<T>
.
Quand vous utilisez un objet [List<T>]
, vous devez créer la liste avec un type spécifique, comme [String]
ou [Int]
. Quand vous ajoutez des objets d’un type différent à la liste, ils sont castés vers le type spécifié. S’ils ne peuvent pas être castés dans le type spécifié, la méthode lève une exception.
$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
Quand vous avez besoin que la liste soit une collection de différents types d’objets, créez-la avec [Object]
comme type de liste. Vous pouvez énumérer la collection et inspecter les types des objets qu’elle contient.
$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
Si vous avez besoin d’un tableau, vous pouvez appeler la méthode ToArray()
sur la liste ou laisser PowerShell créer le tableau à votre place :
$results = @(
Get-Something
Get-SomethingElse
)
Dans cet exemple, PowerShell crée un [ArrayList]
pour stocker les résultats écrits dans le pipeline à l’intérieur de l’expression de tableau. Juste avant l’affectation à $results
, PowerShell convertit [ArrayList]
en [Object[]]
.
Ajout de chaîne
Les chaînes sont immuables. Chaque ajout à la chaîne crée en fait une nouvelle chaîne suffisamment grande pour contenir tout le contenu des opérandes de gauche et de droite, puis copie les éléments des deux opérandes dans la nouvelle chaîne. Pour les chaînes de petite taille, cette surcharge peut ne pas avoir d’importance. Pour les chaînes volumineuses, cela peut affecter les performances et la consommation de mémoire.
Il existe au moins deux alternatives :
- L’opérateur
-join
concatène des chaînes - La classe .NET
[StringBuilder]
fournit une chaîne mutable
L’exemple suivant compare les performances de ces trois méthodes de création d’une chaîne.
$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'
}
}
}
Ces tests ont été exécutés dans PowerShell 7.4.2 sur une machine Windows 11. Les résultats montrent que l’opérateur -join
est le plus rapide, suivi de la classe [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
Les temps et les vitesses relatives peuvent varier en fonction du matériel, de la version de PowerShell et de la charge de travail actuelle sur le système.
Traitement des fichiers volumineux
La méthode idiomatique pour traiter un fichier dans PowerShell peut ressembler à ceci :
Get-Content $path | Where-Object Length -GT 10
Ceci peut s’avérer beaucoup plus lent que d’utiliser directement des API .NET. Par exemple, vous pouvez utiliser 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()
}
}
Vous pouvez également utiliser la méthode ReadLines
de [System.IO.File]
, qui inclut StreamReader
dans un wrapper et simplifie le processus de lecture :
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Recherche d’entrées par propriété dans des collections volumineuses
Il est courant d’avoir à utiliser une propriété partagée pour identifier le même enregistrement dans différentes collections, comme l’utilisation d’un nom pour récupérer un ID d’une liste et un e-mail d’une autre liste. Faire une itération sur la première liste pour rechercher l’enregistrement correspondant dans la deuxième collection est une opération lente. En particulier, le filtrage répété de la deuxième collection a une surcharge importante.
Examinons deux collections : l’une d’elles a les propriétés ID et Nom, et l’autre a les propriétés Nom et E-mail :
$Employees = 1..10000 | ForEach-Object {
[PSCustomObject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[PSCustomObject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
La méthode habituelle pour rapprocher ces collections afin de retourner une liste d’objets avec les propriétés ID, Nom et E-mail peut ressembler à ceci :
$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
}
}
Toutefois, cette implémentation doit filtrer les 5 000 éléments de la collection $Accounts
, à raison d’une fois pour chaque élément de la collection $Employee
. Cette opération peut prendre plusieurs minutes, même pour une recherche d’une seule valeur comme celle-ci.
Au lieu de cela, vous pouvez créer une table de hachage qui utilise la propriété partagée Nom comme clé et le compte correspondant comme valeur.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Rechercher des clés dans une table de hachage est beaucoup plus rapide que de filtrer une collection par valeurs de propriété. Au lieu de vérifier chaque élément dans la collection, PowerShell peut simplement vérifier si la clé est définie et utiliser sa valeur.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
C’est beaucoup plus rapide. Alors que le filtre en boucle a pris plusieurs minutes, la recherche par hachage prend moins d’une seconde.
Utiliser Write-Host avec précaution
La commande Write-Host
doit être utilisée seulement quand vous devez écrire du texte mis en forme sur la console de l’hôte, au lieu d’écrire des objets dans le pipeline Success (Réussite).
Write-Host
peut être beaucoup plus lent que [Console]::WriteLine()
pour des hôtes spécifiques comme pwsh.exe
, powershell.exe
ou powershell_ise.exe
. Toutefois, il n’est pas garanti que [Console]::WriteLine()
fonctionne sur tous les hôtes. De plus, la sortie écrite à l’aide de [Console]::WriteLine()
n’est pas écrite dans les transcriptions démarrées par Start-Transcript
.
JIT (compilation)
PowerShell compile le code de script en bytecode interprété. À partir de PowerShell 3, pour tout code exécuté de façon répétée dans une boucle, PowerShell peut améliorer les performances en effectuant une compilation JIT (juste-à-temps) du code en code natif.
Les boucles qui comportent moins de 300 instructions sont éligibles pour la compilation JIT. Les boucles plus volumineuses sont trop coûteuses à compiler. Lorsque la boucle s’est exécutée 16 fois, le script est compilé juste-à-temps en arrière-plan. Lorsque la compilation JIT est terminée, l’exécution est transférée au code compilé.
Éviter les appels répétés à une fonction
L’appel d’une fonction peut être une opération coûteuse. Si vous appelez une fonction dans une boucle courte d’exécution longue, envisagez de déplacer la boucle à l’intérieur de la fonction.
Penchez-vous sur les exemples suivants :
$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’exemple Basic for-loop est la ligne de base pour les performances. Le deuxième exemple wrappe le générateur de nombres aléatoires dans une fonction appelée au sein d’une boucle serrée. Le troisième exemple déplace la boucle à l’intérieur de la fonction. La fonction est appelée une seule fois, mais le code génère néanmoins la même quantité de nombres aléatoires. Notez la différence entre les délais d’exécution pour chaque exemple.
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
Éviter de wrapper des pipelines d’applet de commande
La plupart des applets de commande sont implémentées pour le pipeline, qui correspond à une syntaxe et un processus séquentiels. Par exemple :
cmdlet1 | cmdlet2 | cmdlet3
L’initialisation d’un nouveau pipeline peut être coûteuse. Vous devez donc éviter de wrapper un pipeline d’applet de commande dans un autre pipeline existant.
Considérez l'exemple suivant. Le fichier Input.csv
contient 2 100 lignes. La commande Export-Csv
est wrappée dans le pipeline ForEach-Object
. L’applet de commande Export-Csv
est appelée pour chaque itération de la boucle ForEach-Object
.
$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
Pour l’exemple suivant, la commande Export-Csv
a été déplacée en dehors du pipeline ForEach-Object
.
Dans le cas présent, Export-Csv
est appelé une seule fois, mais traite toujours tous les objets passés en dehors de 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’exemple sans inclusion dans un wrapper est 372 fois plus rapide. Notez également que la première implémentation nécessite le paramètre Append, qui n’est pas obligatoire pour la prochaine implémentation.
Utiliser OrderedDictionary pour créer dynamiquement de nouveaux objets
Il existe des situations où nous avons besoin de créer des objets de manière dynamique sur des entrées. Cette façon étant peut-être la plus utilisée pour créer un PSObject, puis ajouter de nouvelles propriétés via l’utilisation de la cmdlet Add-Member
. Les coûts en matière de performances pour de petites collections utilisant cette technique sont dans certains cas négligeables, mais ils peuvent devenir notables pour des collections volumineuses. Dans ce cas, l’approche recommandée consiste à utiliser un [OrderedDictionary]
, puis de le convertir en PSObject en utilisant l’accélérateur de type [pscustomobject]
. Pour obtenir plus d’informations, consultez la section Créer des dictionnaires ordonnés de about_Hash_Tables.
Supposez que vous avez la réponse d’API suivante stockée dans la variable $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" ]
]
}
]
}
Supposez maintenant que vous souhaitez exporter ces données vers un fichier CSV. Vous devez tout d’abord créer de nouveaux objets et ajouter les propriétés et les valeurs en utilisant la 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
}
L’utilisation de OrderedDictionary
permet de traduire le code de la façon suivante :
$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
}
Dans les deux cas, la sortie $result
sera la même :
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
La dernière approche devient exponentiellement plus efficace, car le nombre d’objets et de propriétés membres augmente.
Voici une comparaison des performances des trois techniques pour créer des objets avec 5 propriétés :
$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'
}
}
}
Et voici les résultats :
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