Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Skrypty programu PowerShell, które korzystają bezpośrednio z platformy .NET i unikają potoku, wydają się być szybsze niż idiomatyczny program PowerShell. Idiomatyczny program PowerShell używa poleceń cmdlet i funkcji programu PowerShell, często korzystających z potoku i uciekających się do platformy .NET tylko wtedy, gdy jest to konieczne.
Notatka
Wiele technik opisanych w tym miejscu nie jest idiomatycznych programu PowerShell i może zmniejszyć czytelność skryptu programu PowerShell. Autorzy skryptów powinni używać idiomatycznego PowerShell, chyba że wydajność dyktuje inaczej.
Tłumienie danych wyjściowych
Istnieje wiele sposobów na uniknięcie zapisywania obiektów w potoku.
- Przypisanie lub przekierowanie pliku do
$null
- Rzutowanie do
[void]
- Potok do
Out-Null
Szybkość przypisywania do $null
, rzutowania do [void]
i przekierowywania plików do $null
jest prawie identyczna. Jednak wywoływanie Out-Null
w dużej pętli może być znacznie wolniejsze, zwłaszcza w programie 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'
}
}
}
Te testy zostały uruchomione na maszynie z systemem Windows 11 w programie PowerShell 7.3.4. Poniżej przedstawiono wyniki:
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
Czasy i względne szybkości mogą się różnić w zależności od sprzętu, wersji programu PowerShell i bieżącego obciążenia w systemie.
Dodawanie tablic
Generowanie listy elementów jest często wykonywane przy użyciu tablicy z operatorem dodawania:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Notatka
W programie PowerShell 7.5 dodanie tablicy zostało zoptymalizowane i nie tworzy już nowej tablicy dla każdej operacji. Zagadnienia dotyczące wydajności opisane tutaj nadal dotyczą wersji programu PowerShell wcześniejszych niż 7.5. Aby uzyskać więcej informacji, zobacz Co nowego w programie PowerShell 7.5.
Dodawanie tablic może być nieefektywne, ponieważ tablice mają stały rozmiar. Każde dodanie do tablicy tworzy nową tablicę, wystarczająco dużą, aby pomieścić wszystkie elementy zarówno z lewego, jak i prawego operandu. Elementy każdego z dwóch operandów są kopiowane do nowej tablicy. W przypadku małych kolekcji to obciążenie może nie mieć znaczenia. Wydajność może ucierpieć w przypadku dużych kolekcji.
Istnieje kilka alternatyw. Jeśli w rzeczywistości nie potrzebujesz tablicy, rozważ użycie wpisanej listy ogólnej ([List<T>]
):
$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
Wpływ na wydajność użycia dodawania do tablic rośnie wykładniczo wraz z rozmiarem kolekcji i liczbą dodanych elementów. Ten kod porównuje jawne przypisywanie wartości do tablicy z użyciem operacji dodawania tablic oraz użycie metody Add(T)
w obiekcie [List<T>]
. Definiuje jawne przypisanie jako podstawę dla wydajności.
$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'
}
}
}
Te testy zostały uruchomione na maszynie z systemem Windows 11 w programie 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
Podczas pracy z dużymi kolekcjami dodawanie tablic jest znacznie wolniejsze niż dodawanie do List<T>
.
W przypadku korzystania z obiektu [List<T>]
należy utworzyć listę z określonym typem, na przykład [string]
lub [int]
. Po dodaniu obiektów innego typu do listy są one rzutowane do określonego typu. Jeśli nie można ich rzutować na określony typ, metoda zgłasza wyjątek.
$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
Gdy lista musi być kolekcją różnych typów obiektów, utwórz ją przy użyciu [Object]
jako typu listy. Możesz wyliczyć kolekcję i sprawdzić typy obiektów w niej.
$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
Jeśli potrzebujesz tablicy, możesz wywołać metodę ToArray()
na liście lub umożliwić programowi PowerShell utworzenie tablicy dla Ciebie:
$results = @(
Get-Something
Get-SomethingElse
)
W tym przykładzie program PowerShell tworzy [ArrayList]
do przechowywania wyników zapisanych w potoku wewnątrz wyrażenia tablicy. Tuż przed przypisaniem do $results
program PowerShell konwertuje [ArrayList]
na [Object[]]
.
Dodawanie ciągu
Ciągi są niezmienne. Każdy dodatek do ciągu faktycznie tworzy nowy ciąg wystarczająco duży, aby przechowywać zawartość zarówno lewych, jak i prawych operandów, a następnie kopiuje elementy obu operandów do nowego ciągu. W przypadku małych ciągów to obciążenie może nie mieć znaczenia. W przypadku dużych ciągów może to mieć wpływ na wydajność i zużycie pamięci.
Istnieją co najmniej dwie alternatywy:
- Operator
-join
łączy ciągi - Klasa .NET
[StringBuilder]
udostępnia modyfikowalny ciąg
Poniższy przykład porównuje wydajność tych trzech metod tworzenia ciągu.
$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'
}
}
}
Te testy zostały uruchomione na maszynie z systemem Windows 11 w programie PowerShell 7.4.2. Wyniki pokazują, że operator -join
jest najszybszy, a następnie klasa [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
Czasy i względne szybkości mogą się różnić w zależności od sprzętu, wersji programu PowerShell i bieżącego obciążenia w systemie.
Przetwarzanie dużych plików
Idiomatyczny sposób przetwarzania pliku w programie PowerShell może wyglądać następująco:
Get-Content $path | Where-Object Length -GT 10
To może być o rząd wielkości wolniejsze niż bezpośrednie korzystanie z interfejsów API platformy .NET. Na przykład możesz użyć klasy .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()
}
}
Można również użyć metody ReadLines
[System.IO.File]
, która opakowuje StreamReader
, upraszcza proces odczytywania:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Szukanie wpisów według właściwości w dużych kolekcjach
Często należy używać właściwości udostępnionej do identyfikowania tego samego rekordu w różnych kolekcjach, na przykład przy użyciu nazwy w celu pobrania identyfikatora z jednej listy i wiadomości e-mail z innej. Iterowanie na pierwszej liście w celu znalezienia pasującego rekordu w drugiej kolekcji jest powolne. W szczególności powtarzające się filtrowanie drugiej kolekcji ma duże obciążenie.
Dla dwóch kolekcji, jednej z identyfikatorem i nazwą , drugiej z nazwą i e-mailem :
$Employees = 1..10000 | ForEach-Object {
[pscustomobject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[pscustomobject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
Zwykły sposób uzgadniania tych kolekcji, aby zwrócić listę obiektów z właściwościami: identyfikator , nazwa oraz e-mail , może wyglądać następująco:
$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
}
}
Jednak ta implementacja musi filtrować wszystkie 5000 elementów w kolekcji $Accounts
raz dla każdego elementu w kolekcji $Employee
. Może to potrwać kilka minut, nawet w przypadku tego wyszukiwania pojedynczej wartości.
Zamiast tego możesz utworzyć Tablicę skrótów, która używa udostępnionej właściwości Name jako klucza i pasującego konta jako wartości.
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Szukanie kluczy w tabeli skrótów jest znacznie szybsze niż filtrowanie kolekcji według wartości właściwości. Zamiast sprawdzać każdy element w kolekcji, program PowerShell może sprawdzić, czy klucz jest zdefiniowany i używać jego wartości.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Jest to znacznie szybsze. Chociaż działanie filtra pętli zajęło kilka minut, wyszukiwanie skrótu trwa mniej niż sekundę.
Ostrożnie używaj Write-Host
Polecenia Write-Host
należy używać tylko wtedy, gdy trzeba zapisać sformatowany tekst w konsoli hosta, zamiast zapisywać obiekty w potoku Success.
Write-Host
może być o rząd wielkości wolniejsze niż [Console]::WriteLine()
dla określonych hostów, takich jak pwsh.exe
, powershell.exe
, lub powershell_ise.exe
. Jednak [Console]::WriteLine()
nie ma gwarancji, że działa we wszystkich hostach. Ponadto dane wyjściowe zapisywane przy użyciu [Console]::WriteLine()
nie są zapisywane w transkrypcjach rozpoczętych przez Start-Transcript
.
kompilacja JIT
Program PowerShell kompiluje kod skryptu do kodu bajtowego, który jest interpretowany. Począwszy od programu PowerShell 3, w przypadku kodu, który jest wielokrotnie wykonywany w pętli, program PowerShell może zwiększyć wydajność dzięki kompilowaniu kodu just in time (JIT) do kodu natywnego.
Pętle z mniej niż 300 instrukcjami kwalifikują się do kompilacji JIT. Pętle większe od tych są zbyt kosztowne do skompilowania. Po uruchomieniu pętli 16 razy, skrypt jest kompilowany metodą JIT w tle. Po zakończeniu kompilacji JIT wykonywanie jest przenoszone do skompilowanego kodu.
Unikaj powtarzających się wywołań funkcji
Wywoływanie funkcji może być kosztowną operacją. Jeśli wywołujesz funkcję w długo działającej wąskiej pętli, rozważ umieszczenie pętli wewnątrz funkcji.
Rozważmy następujące przykłady:
$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'
}
}
}
Przykład podstawowej pętli for stanowi punkt odniesienia dla wydajności. Drugi przykład opakowuje generator liczb losowych w funkcji, która jest wywoływana w ciasnej pętli. Trzeci przykład przenosi pętlę wewnątrz funkcji. Funkcja jest wywoływana tylko raz, ale kod nadal generuje tę samą liczbę losowych liczb. Zwróć uwagę na różnicę czasu wykonywania dla każdego przykładu.
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
Unikaj zawijania potoków cmdletów
Większość poleceń cmdlet jest implementowanych dla potoku, co jest sekwencyjną składnią i procesem. Na przykład:
cmdlet1 | cmdlet2 | cmdlet3
Inicjowanie nowego potoku może być kosztowne, dlatego należy unikać łączenia potoku cmdlet z innym istniejącym potokiem.
Rozważmy poniższy przykład. Plik Input.csv
zawiera 2100 wierszy. Polecenie Export-Csv
jest zawarte w potoku ForEach-Object
. Polecenie cmdlet Export-Csv
jest wywoływane dla każdej iteracji pętli 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
W następnym przykładzie polecenie Export-Csv
zostało przeniesione poza potok ForEach-Object
.
W takim przypadku Export-Csv
jest wywoływana tylko raz, ale nadal przetwarza wszystkie obiekty 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
Rozpakowany przykład jest 372 razy szybszy. Należy również zauważyć, że pierwsza implementacja wymaga parametru Append, który nie jest wymagany do późniejszej implementacji.
Tworzenie obiektu
Tworzenie obiektów przy użyciu polecenia cmdlet New-Object
może być powolne. Poniższy kod porównuje wydajność tworzenia obiektów przy użyciu polecenia cmdlet New-Object
do akceleratora typu [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
Program PowerShell 5.0 dodał metodę statyczną new()
dla wszystkich typów platformy .NET. Poniższy kod porównuje wydajność tworzenia obiektów przy użyciu polecenia cmdlet New-Object
do metody 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
Używanie elementu OrderedDictionary do dynamicznego tworzenia nowych obiektów
Istnieją sytuacje, w których może być konieczne dynamiczne tworzenie obiektów na podstawie niektórych danych wejściowych, być może najczęściej używanym sposobem utworzenia nowego PSObject, a następnie dodania nowych właściwości przy użyciu polecenia cmdlet Add-Member
. Koszt wydajności małych kolekcji korzystających z tej techniki może być niewielki, jednak może stać się bardzo zauważalny w przypadku dużych kolekcji. W takim przypadku zalecaną metodą jest użycie [OrderedDictionary]
, a następnie przekonwertowanie go na PSObject przy użyciu akceleratora typu [pscustomobject]
. Aby uzyskać więcej informacji, zobacz sekcję Tworzenie uporządkowanych słownikówabout_Hash_Tables.
Załóżmy, że masz następującą odpowiedź interfejsu API przechowywaną w zmiennej $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" ]
]
}
]
}
Teraz załóżmy, że chcesz wyeksportować te dane do pliku CSV. Najpierw należy utworzyć nowe obiekty i dodać właściwości i wartości przy użyciu polecenia 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
}
Przy użyciu OrderedDictionary
kod można przetłumaczyć 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
}
W obu przypadkach dane wyjściowe $result
byłyby takie same:
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
Drugie podejście staje się bardziej wydajne wykładniczo, ponieważ liczba obiektów i właściwości składowych wzrasta.
Poniżej przedstawiono porównanie wydajności trzech technik tworzenia obiektów z 5 właściwościami:
$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 oto wyniki:
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