Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Skripty PowerShell, které využívají rozhraní .NET přímo a vyhýbají se pipeline, bývají rychlejší než idiomatický PowerShell. Idiomatic PowerShell používá cmdlety a funkce PowerShellu, často využívá kanál a k .NET se uchyluje pouze v případě potřeby.
Poznámka
Mnohé z technik popsaných zde nejsou idiomatické v PowerShellu a mohou snížit čitelnost PowerShellového skriptu. Autořům skriptů se doporučuje, aby používali idiomatický PowerShell, pokud tomu výkon nebrání.
Potlačení výstupu
Existuje mnoho způsobů, jak zabránit zápisu objektů do kanálu.
- Přiřazení nebo přesměrování souboru na
$null - Přetypování na
[void] - Potrubí k
Out-Null
Rychlosti přiřazování $null, přetypování na [void]a přesměrování souborů na $null jsou téměř stejné. Volání Out-Null ve velké smyčce ale může být výrazně pomalejší, zejména v PowerShellu 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'
}
}
}
Tyto testy byly spuštěny na počítači s Windows 11 v PowerShellu 7.3.4. Výsledky jsou uvedené níže:
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
Časy a relativní rychlosti se mohou lišit v závislosti na hardwaru, verzi PowerShellu a aktuální úloze v systému.
Přidání pole
Generování seznamu položek se často provádí pomocí pole s operátorem sčítání:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Poznámka
V PowerShellu 7.5 bylo přidání pole optimalizováno a už nevytváří pro každou operaci nové pole. Zde popsané aspekty výkonu se stále vztahují na verze PowerShellu starší než 7.5. Další informace najdete v tématu Co je nového v PowerShellu 7.5.
Přidání pole je neefektivní, protože pole mají pevnou velikost. Každé přidání do pole vytvoří nové pole dostatečně velké pro uložení všech prvků levého i pravého operandu. Prvky obou operandů se zkopírují do nového pole. U malých kolekcí nemusí být tento překryv důležitý. Výkon může být u velkých kolekcí snížen.
Existuje několik alternativ. Pokud pole ve skutečnosti nepotřebujete, zvažte použití zadaného obecného seznamu ([List<T>]):
$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
Dopad použití přidávání prvků do pole na výkon se exponenciálně zvětšuje s velikostí kolekce a počtem přidání. Tento kód porovnává explicitně přiřazování hodnot k poli pomocí sčítání pole a použití metody Add(T) u objektu [List<T>]. Definuje explicitní přiřazení jako základ pro výkon.
$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'
}
}
}
Tyto testy byly spuštěny na počítači s Windows 11 v PowerShellu 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
Při práci s velkými kolekcemi je přidávání do pole výrazně pomalejší než přidávání do List<T>.
Při použití objektu [List<T>] je nutné vytvořit seznam s určitým typem, například [string] nebo [int]. Když do seznamu přidáte objekty jiného typu, přetypují se na zadaný typ. Pokud je nelze přetypovat na zadaný typ, metoda vyvolá výjimku.
$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
Pokud potřebujete, aby byl seznam kolekcí různých typů objektů, vytvořte ho s [Object] jako typ seznamu. Můžete sejmout seznam kolekce a zkontrolovat typy objektů v ní.
$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
Pokud potřebujete pole, můžete volat metodu ToArray() v seznamu nebo můžete nechat PowerShell vytvořit pole za vás:
$results = @(
Get-Something
Get-SomethingElse
)
V tomto příkladu PowerShell vytvoří [ArrayList] pro uložení výsledků zapsaných do kanálu uvnitř výrazu pole. Těsně před přiřazením k $resultsPowerShell převede [ArrayList] na [Object[]].
Sčítání řetězců
Řetězce jsou neměnné. Každý dodatek k řetězci ve skutečnosti vytvoří nový řetězec dostatečně velký, aby držel obsah levého i pravého operandu a potom zkopíruje prvky obou operandů do nového řetězce. U malých řetězců nemusí být tato režie důležitá. U velkých řetězců to může mít vliv na výkon a spotřebu paměti.
Existují alespoň dvě alternativy:
- Operátor
-joinzřetězí řetězce - Třída
[StringBuilder].NET poskytuje proměnlivý řetězec.
Následující příklad porovnává výkon těchto tří metod sestavení řetězce.
$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'
}
}
}
Tyto testy byly spuštěny na počítači s Windows 11 v PowerShellu 7.4.2. Výstup ukazuje, že operátor -join je nejrychlejší, následovaný [StringBuilder] třídou.
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
Časy a relativní rychlosti se mohou lišit v závislosti na hardwaru, verzi PowerShellu a aktuální úloze v systému.
Zpracování velkých souborů
Idiotický způsob zpracování souboru v PowerShellu může vypadat nějak takto:
Get-Content $path | Where-Object Length -GT 10
Může to být řádově pomalejší než přímé použití rozhraní .NET API. Můžete například použít třídu .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()
}
}
Můžete také použít ReadLines metodu [System.IO.File], která obaluje StreamReader a zjednodušuje proces čtení.
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Vyhledávání položek podle vlastností ve velkých kolekcích
K identifikaci stejného záznamu v různých kolekcích je běžné použít sdílenou vlastnost, například použití názvu k načtení ID z jednoho seznamu a e-mailu z jiného. Iterace přes první seznam k vyhledání odpovídajícího záznamu ve druhé kolekci je pomalá. Zejména opakované filtrování druhé kolekce představuje velkou režii.
Pokud máte dvě kolekce, jednu s ID a názvem, druhou s názvem a e-mailem:
$Employees = 1..10000 | ForEach-Object {
[pscustomobject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[pscustomobject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
Obvyklý způsob, jak tyto kolekce odsouhlasit, aby se vrátil seznam objektů s vlastnostmi ID, Název a E-mail , může vypadat takto:
$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
}
}
Tato implementace však musí filtrovat všech 5000 položek v kolekci $Accounts jednou pro každou položku v kolekci $Employee. To může trvat minuty, dokonce i pro toto vyhledávání s jedinou hodnotou.
Místo toho můžete vytvořit tabulku hash, která jako klíč používá sdílenou vlastnost Název a odpovídající účet jako hodnotu.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Vyhledávání klíčů v tabulce hash je mnohem rychlejší než filtrování kolekce podle hodnot vlastností. Místo kontroly každé položky v kolekci může PowerShell zkontrolovat, jestli je klíč definovaný, a použít jeho hodnotu.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
To je mnohem rychlejší. Zatímco dokončení cyklického filtru trvalo několik minut, vyhledávání pomocí hash tabulky trvá méně než sekundu.
Pečlivě používejte Write-Host
Příkaz Write-Host by se měl použít jenom v případě, že potřebujete napsat formátovaný text do hostitelské konzoly, a ne psát objekty do kanálu Success.
Write-Host může být řádově pomalejší než [Console]::WriteLine() pro konkrétní hostitele, jako jsou pwsh.exe, powershell.exenebo powershell_ise.exe. Není však zaručeno, že [Console]::WriteLine() bude fungovat ve všech hostitelích. Výstup napsaný pomocí [Console]::WriteLine() se také nezapisuje do přepisů zahájených Start-Transcript.
kompilace JIT
PowerShell zkompiluje kód skriptu do bajtového kódu, který se interpretuje. Od verze PowerShell 3 může PowerShell pro kód, který se opakovaně spouští ve smyčce, zlepšit výkon tím, že kód kompiluje jako nativní kód pomocí Just-in-time (JIT) kompilace.
Smyčky, které mají méně než 300 instrukcí, mají nárok na kompilaci JIT. Smyčky větší než toto jsou příliš nákladné na kompilaci. Když se smyčka spustí 16krát, skript se zkompiluje na pozadí. Po dokončení kompilace JIT se provádění přenese do zkompilovaného kódu.
Vyhněte se opakovaným voláním funkce
Volání funkce může být náročná operace. Pokud voláte funkci v dlouhotrvající těsné smyčce, zvažte přesunutí smyčky uvnitř funkce.
Podívejte se na následující příklady:
$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'
}
}
}
Příkladem Basic for-loop je základní čára výkonu. Druhý příklad ukazuje generátor náhodných čísel zabalený do funkce, která je volána v úzké smyčce. Třetí příklad přesune smyčku dovnitř funkce. Funkce se volá pouze jednou, ale kód stále generuje stejné množství náhodných čísel. Všimněte si rozdílu v časech provádění pro každý příklad.
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
Vyhněte se obtékání kanálů cmdletů
Většina cmdletů je implementována pro datový proud, což je sekvenční syntaxe a proces. Například:
cmdlet1 | cmdlet2 | cmdlet3
Inicializace nového kanálu může být nákladná, proto byste se měli vyhnout zabalení kanálu příkazů cmdlet do jiného, již existujícího kanálu.
Podívejte se na následující příklad. Soubor Input.csv obsahuje 2100 řádků. Příkaz Export-Csv je zabalený uvnitř kanálu ForEach-Object. Rutina cmdlet Export-Csv se vyvolá pro každou iteraci smyčky 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
V dalším příkladu se příkaz Export-Csv přesunul mimo kanál ForEach-Object.
V tomto případě se Export-Csv vyvolá pouze jednou, ale stále zpracovává všechny objekty předané z 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
Příklad rozbalení je 372-krát rychlejší. Všimněte si také, že první implementace vyžaduje parametr Append, který není vyžadován pro pozdější implementaci.
Vyhněte se nepotřebným výčtům kolekcí
Operátory porovnání PowerShellu mají při porovnávání kolekcí funkci konvience. Pokud je hodnota vlevo ve výrazu kolekce, vrátí operátor prvky kolekce, které odpovídají pravé hodnotě výrazu.
Tato funkce poskytuje jednoduchý způsob filtrování kolekce. Například:
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
Pokud však použijete porovnání kolekce v podmíněném příkazu, který očekává pouze logický výsledek, může tato funkce vést k nízkému výkonu.
Příklad:
if ($Collection -like '*1*') { 'Found' }
V tomto příkladu PowerShell porovná pravou hodnotu s každou hodnotou v kolekci a vrátí kolekci výsledků. Vzhledem k tomu, že výsledek není prázdný, výsledek, který není null, se vyhodnotí jako $true. Podmínka platí, když se najde první shoda, ale PowerShell stále vytvoří výčet celé kolekce. Tento výčet může mít významný dopad na výkon u velkých kolekcí.
Jedním ze způsobů, jak zvýšit výkon, je použít Where() metodu kolekce. Metoda Where() přestane vyhodnocovat kolekci, jakmile najde první shodu.
# 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
U milionů položek je použití Where() metody výrazně rychlejší.
Vytvoření objektu
Vytváření objektů pomocí rutiny New-Object může být pomalé. Následující kód porovnává výkon vytváření objektů pomocí rutiny New-Object s akcelerátorem typů [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 přidal new() statickou metodu pro všechny typy .NET. Následující kód porovnává výkon vytváření objektů pomocí rutiny New-Object s new() metodou.
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
Použití OrderedDictionary k dynamickému vytváření nových objektů
Existují situace, kdy možná budeme muset dynamicky vytvářet objekty na základě určitého vstupu, možná nejčastěji používaný způsob, jak vytvořit nový PSObject a pak přidat nové vlastnosti pomocí rutiny Add-Member. Náklady na výkon malých kolekcí používající tuto techniku mohou být zanedbatelné, ale u velkých kolekcí se můžou velmi znatelné. V takovém případě doporučujeme použít [OrderedDictionary] a pak ho převést na PSObject pomocí akcelerátoru typu [pscustomobject]. Další informace najdete v části Vytváření uspořádaných slovníkůabout_Hash_Tables.
Předpokládejme, že máte uloženou následující odpověď rozhraní API v proměnné $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" ]
]
}
]
}
Předpokládejme, že teď chcete tato data exportovat do CSV. Nejprve musíte vytvořit nové objekty a přidat vlastnosti a hodnoty pomocí rutiny 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
}
Pomocí OrderedDictionarylze kód přeložit na:
$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
}
V obou případech by byl výstup $result stejný:
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
Druhý přístup se stává exponenciálně efektivnějším s rostoucím počtem objektů a vlastností členů.
Tady je porovnání výkonu tří technik pro vytváření objektů s 5 vlastnostmi:
$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'
}
}
}
A to jsou výsledky:
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