PowerShell 腳本效能考慮
直接運用 .NET 的 PowerShell 腳本,並避免管線的速度往往比慣用的 PowerShell 快。 慣用 PowerShell 使用 Cmdlet 和 PowerShell 函式,通常會利用管線,並在必要時才訴諸 .NET。
注意
此處所述的許多技術並非慣用的PowerShell,而且可能會減少PowerShell腳本的可讀性。 除非效能另有規定,否則建議腳本作者使用慣用的 PowerShell。
隱藏輸出
有許多方式可以避免將物件寫入管線。
- 指派或檔案重新導向至
$null
- 轉型至
[void]
- 管道
Out-Null
指派給 $null
、轉換成 [void]
,以及將檔案重新導向至 $null
的速度幾乎完全相同。 不過,在大型迴圈中呼叫 Out-Null
可能會明顯變慢,特別是在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'
}
}
}
這些測試是在PowerShell 7.3.4的 Windows 11 電腦上執行。 結果如下所示:
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
時間與相對速度可能會因硬體、PowerShell 版本和系統上目前的工作負載而有所不同。
陣列新增
產生項目清單通常會使用具有加法運算子的陣列來完成:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
數位新增沒有效率,因為陣列的大小固定。 陣列的每個新增都會建立足夠大的新陣列,以容納左右操作數的所有元素。 這兩個操作數的項目都會複製到新的數位中。 對於小型集合,此額外負荷可能無關緊要。 大型集合的效能可能會受到影響。
有幾個替代方案。 如果您實際上不需要數位,請考慮使用具類型的泛型清單 ([List<T>]
):
$results = [System.Collections.Generic.List[object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
使用陣列加法的效能影響會隨著集合大小和數位加法以指數方式成長。 此程式代碼會比較明確將值指派給陣列,以及在 [List<T>]
物件上使用 Add(T)
方法。 它會將明確指派定義為效能的基準。
$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'
}
}
}
這些測試是在PowerShell 7.3.4的 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
當您使用大型集合時,陣列新增的速度會比新增至 List<T>
大幅慢。
使用 [List<T>]
物件時,您必須建立具有特定類型的清單,例如 [String]
或 [Int]
。 當您將不同類型的物件新增至清單時,它們會轉換成指定的類型。 如果無法轉換成指定的型別,方法會引發例外狀況。
$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
當您需要清單是不同類型的物件集合時,請使用 [Object]
作為清單類型來建立清單。 您可以列舉集合,檢查其中對象的類型。
$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
如果您需要陣列,您可以在清單上呼叫 ToArray()
方法,或者您可以讓 PowerShell 為您建立陣列:
$results = @(
Get-Something
Get-SomethingElse
)
在此範例中,PowerShell 會建立 [ArrayList]
,以保存寫入數位表達式內管線的結果。 在指派給 $results
之前,PowerShell 會將 [ArrayList]
轉換成 [Object[]]
。
字串新增
字串是不可變的。 每個新增字串實際上都會建立足以容納左右操作數內容的新字串,然後將這兩個操作數的元素複製到新的字串中。 對於小型字串,此額外負荷可能無關緊要。 對於大型字串,這可能會影響效能和記憶體耗用量。
至少有兩個替代方案:
-
-join
運算子 串連字串 - .NET
[StringBuilder]
類別提供可變字串
下列範例會比較這三種方法建置字串的效能。
$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'
}
}
}
這些測試是在PowerShell 7.4.2的 Windows 11 電腦上執行。 輸出顯示 -join
運算符是最快的運算子,後面接著 [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
時間與相對速度可能會因硬體、PowerShell 版本和系統上目前的工作負載而有所不同。
處理大型檔案
在 PowerShell 中處理檔案的慣用方式可能如下所示:
Get-Content $path | Where-Object Length -GT 10
這可能會比直接使用 .NET API 慢一些。 例如,您可以使用 .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()
}
}
您也可以使用 [System.IO.File]
的 ReadLines
方法,這個方法會包裝 StreamReader
,簡化讀取程式:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
依大型集合中的 屬性查閱專案
通常需要使用共用屬性來識別不同集合中的相同記錄,例如使用名稱從一個清單擷取標識符,並從另一個清單中擷取電子郵件。 逐一查看第一個清單,以尋找第二個集合中的相符記錄速度很慢。 特別是,第二個集合的重複篩選具有很大的額外負荷。
假設有兩個集合,一個具有 標識符 和 Name,另一個具有 Name 和 Email:
$Employees = 1..10000 | ForEach-Object {
[PSCustomObject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[PSCustomObject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
協調這些集合以傳回具有 標識子、Name和 Email 屬性之物件清單的一般方式可能如下所示:
$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
}
}
不過,該實作必須針對 $Employee
集合中的每個專案篩選 $Accounts
集合中的所有 5000 個專案一次。 這可能需要幾分鐘的時間,即使是針對這個單一值查閱。
相反地,您可以建立使用共用 Name 屬性做為索引鍵和相符帳戶做為值的 哈希表。
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
查閱哈希表中的索引鍵比依屬性值篩選集合快得多。 PowerShell 可以檢查索引鍵是否已定義並使用其值,而不是檢查集合中的每個專案。
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
這要快得多。 雖然迴圈篩選需要幾分鐘的時間才能完成,但哈希查閱需要不到一秒的時間。
小心使用 Write-Host
只有當您需要將格式化文字寫入主機控制台時,才應該使用 Write-Host
命令,而不是將物件寫入至 Success 管線。
Write-Host
對於特定主機而言,pwsh.exe
、powershell.exe
或 powershell_ise.exe
等特定主機的 [Console]::WriteLine()
速度可能比 [Console]::WriteLine()
慢。 不過,不保證 [Console]::WriteLine()
在所有主機中都能運作。 此外,使用 [Console]::WriteLine()
撰寫的輸出不會寫入 Start-Transcript
開始的文字記錄。
JIT 編譯
PowerShell 會將腳本程式代碼編譯為解譯的位元組程序代碼。 從 PowerShell 3 開始,針對迴圈中重複執行的程式代碼,PowerShell 可以藉由 Just-In-Time (JIT) 將程式代碼編譯成機器碼來改善效能。
少於 300 個指令的循環有資格進行 JIT 編譯。 大於成本過高而無法編譯的迴圈。 當迴圈執行 16 次時,腳本會在背景中以 JIT 編譯。 當 JIT 編譯完成時,執行會傳送至編譯的程序代碼。
避免重複呼叫函式
呼叫函式可能是昂貴的作業。 如果您要在長時間執行的緊密迴圈中呼叫函式,請考慮在函式內移動迴圈。
請考慮下列範例:
$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'
}
}
}
Basic for-loop 範例是效能的基線。 第二個範例會將隨機數產生器包裝在緊密迴圈中呼叫的函式中。 第三個範例會在函式內移動迴圈。 函式只會呼叫一次,但程式代碼仍會產生相同數量的隨機數。 請注意每個範例的執行時間差異。
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
避免包裝 Cmdlet 管線
大部分的 Cmdlet 都是針對管線實作,這是循序語法和程式。 例如:
cmdlet1 | cmdlet2 | cmdlet3
初始化新的管線可能很昂貴,因此您應該避免將 Cmdlet 管線包裝到另一個現有的管線。
請考慮下列範例。
Input.csv
檔案包含 2100 行。
Export-Csv
命令會包裝在 ForEach-Object
管線內。
Export-Csv
Cmdlet 會針對 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
在下一個範例中,Export-Csv
命令已移至 ForEach-Object
管線之外。
在此情況下,只會叫用 Export-Csv
一次,但仍會處理從 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
未包裝的範例 372 倍,。 此外,請注意,第一個實作需要 Append 參數,這在稍後的實作並非必要。
物件建立
使用 New-Object
Cmdlet 建立物件可能會很慢。 下列程式代碼會比較使用 New-Object
Cmdlet 建立物件的效能與 [pscustomobject]
類型加速器。
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 已為所有 .NET 類型新增 new()
靜態方法。 下列程式代碼會比較使用 New-Object
Cmdlet 建立物件的效能與 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
使用 OrderedDictionary 動態建立新的物件
在某些情況下,我們可能需要根據某些輸入動態建立物件,這或許最常用來建立新的 PSObject,然後使用 Add-Member
Cmdlet 新增屬性。 使用這項技術的小型集合效能成本可能微不足道,但對於大型集合而言,可能會變得非常明顯。 在此情況下,建議的方法是使用 [OrderedDictionary]
,然後使用 [pscustomobject]
類型加速器將它轉換成 PSObject。 如需詳細資訊,請參閱 about_Hash_Tables的 建立已排序字典 一節。
假設您有下列 API 回應儲存在 變數 $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" ]
]
}
]
}
現在,假設您想要將此數據匯出至 CSV。 首先,您需要建立新的物件,並使用 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
}
使用 OrderedDictionary
,程式代碼可以轉譯為:
$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
}
在這兩種情況下,$result
輸出會相同:
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
當物件數目和成員屬性增加時,後者會以指數方式變得更有效率。
以下是使用 5 個屬性建立物件的三種技術效能比較:
$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'
}
}
}
以下是結果:
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