Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Os scripts do PowerShell que aproveitam o .NET diretamente e evitam o pipeline tendem a ser mais rápidos do que o PowerShell idiomático. O PowerShell idiomático usa cmdlets e funções do PowerShell, muitas vezes aproveitando o pipeline e recorrendo ao .NET apenas quando necessário.
Observação
Muitas das técnicas descritas aqui não são idiomáticas do PowerShell e podem reduzir a legibilidade de um script do PowerShell. Os autores de scripts são aconselhados a usar o PowerShell idiomático, a menos que o desempenho determine o contrário.
Supressão de saída
Há muitas maneiras de evitar escrever objetos no pipeline.
- Atribuição ou redirecionamento de arquivo para
$null - Casting para
[void] - Tubo para
Out-Null
As velocidades de atribuição a $null, transmissão para [void]e redirecionamento de arquivos para $null são quase idênticas. No entanto, chamar Out-Null em um loop grande pode ser significativamente mais lento, especialmente no 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'
}
}
}
Esses testes foram executados em uma máquina Windows 11 no PowerShell 7.3.4. Os resultados são apresentados abaixo:
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
Os tempos e as velocidades relativas podem variar dependendo do hardware, da versão do PowerShell e da carga de trabalho atual no sistema.
Adição de matriz
A geração de uma lista de itens geralmente é feita usando uma matriz com o operador de adição:
$results = @()
$results += Get-Something
$results += Get-SomethingElse
$results
Observação
No PowerShell 7.5, a adição de matriz foi otimizada e não cria mais uma nova matriz para cada operação. As considerações de desempenho descritas aqui ainda se aplicam às versões do PowerShell anteriores à 7.5. Para obter mais informações, consulte O que há de novo no PowerShell 7.5.
A adição de matrizes é ineficiente porque elas têm um tamanho fixo. Cada adição à matriz cria uma nova matriz grande o suficiente para conter todos os elementos dos operandos esquerdo e direito. Os elementos de ambos os operandos são copiados para a nova matriz. Para pequenas coleções, essa sobrecarga pode não importar. O desempenho pode ser prejudicado por grandes coleções.
Existem algumas alternativas. Se você realmente não precisar de uma matriz, considere usar uma lista genérica digitada ([List<T>]):
$results = [System.Collections.Generic.List[Object]]::new()
$results.AddRange((Get-Something))
$results.AddRange((Get-SomethingElse))
$results
O impacto no desempenho do uso da adição de matriz cresce exponencialmente com o tamanho da coleção e as adições de número. Esse código compara explicitamente a atribuição de valores a uma matriz com o uso da adição de matriz e o uso do método Add(T) em um objeto [List<T>]. Ele define atribuição explícita como a linha de base para o desempenho.
$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'
}
}
}
Esses testes foram executados em uma máquina Windows 11 no 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 você está trabalhando com coleções grandes, a adição de array é dramaticamente mais lenta do que a adição a um List<T>.
Ao usar um objeto [List<T>], você precisa criar a lista com um tipo específico, como [string] ou [int]. Quando você adiciona objetos de um tipo diferente à lista, eles são convertidos para o tipo especificado. Se eles não puderem ser convertidos para o tipo especificado, o método gerará uma exceção.
$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 você precisar que a lista seja uma coleção de diferentes tipos de objetos, crie-a com [Object] como o tipo de lista. Você pode enumerar a coleção, inspecionar os tipos dos objetos nela.
$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 você precisar de uma matriz, poderá chamar o método ToArray() na lista ou permitir que o PowerShell crie a matriz para você:
$results = @(
Get-Something
Get-SomethingElse
)
Neste exemplo, o PowerShell cria um [ArrayList] para armazenar os resultados gravados no pipeline dentro da expressão de matriz. Pouco antes de atribuir ao $results, o PowerShell converte o [ArrayList] em um [Object[]].
Adição de cadeia de caracteres
As cadeias de caracteres são imutáveis. Cada adição à cadeia de caracteres realmente cria uma nova cadeia de caracteres grande o suficiente para conter o conteúdo dos operandos esquerdo e direito e, em seguida, copia os elementos de ambos os operandos para a nova cadeia de caracteres. Para cordas pequenas, essa sobrecarga pode não importar. Para cadeias de caracteres grandes, isso pode afetar o desempenho e o consumo de memória.
Existem pelo menos duas alternativas:
- O operador
-joinconcatena cadeias de caracteres - A classe .NET
[StringBuilder]fornece uma cadeia de caracteres mutável
O exemplo a seguir compara o desempenho desses três métodos de criação de uma cadeia de caracteres.
$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'
}
}
}
Esses testes foram executados em uma máquina Windows 11 no PowerShell 7.4.2. A saída mostra que o operador -join é o mais rápido, seguido pela 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
Os tempos e as velocidades relativas podem variar dependendo do hardware, da versão do PowerShell e da carga de trabalho atual no sistema.
Processamento de ficheiros grandes
A maneira idiomática de processar um arquivo no PowerShell pode ser algo como:
Get-Content $path | Where-Object Length -GT 10
Isso pode ser uma ordem de grandeza mais lenta do que usar APIs do .NET diretamente. Por exemplo, você pode usar o .NET [StreamReader] classe:
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()
}
}
Você também pode usar o método ReadLines de [System.IO.File], que envolve StreamReader, simplifica o processo de leitura:
foreach ($line in [System.IO.File]::ReadLines($path)) {
if ($line.Length -gt 10) {
$line
}
}
Procurar entradas por propriedade em grandes coleções
É comum precisar usar uma propriedade compartilhada para identificar o mesmo registro em coleções diferentes, como usar um nome para recuperar um ID de uma lista e um e-mail de outra. A iteração sobre a primeira lista para encontrar o registro correspondente na segunda coleção é lenta. Em particular, a filtragem repetida da segunda coleção tem uma grande sobrecarga.
Dadas duas coleções, uma com um Id e Name, a outra com Name e Email:
$Employees = 1..10000 | ForEach-Object {
[pscustomobject]@{
Id = $_
Name = "Name$_"
}
}
$Accounts = 2500..7500 | ForEach-Object {
[pscustomobject]@{
Name = "Name$_"
Email = "Name$_@fabrikam.com"
}
}
A maneira usual de reconciliar essas coleções para retornar uma lista de objetos com as propriedades Id, Name e Email pode ter esta aparência:
$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
}
}
No entanto, essa implementação tem que filtrar todos os 5000 itens na coleção $Accounts uma vez para cada item na coleção $Employee. Isso pode levar minutos, mesmo para essa pesquisa de valor único.
Em vez disso, você pode criar um de Tabela de Hash
$LookupHash = @{}
foreach ($Account in $Accounts) {
$LookupHash[$Account.Name] = $Account
}
Procurar chaves em uma tabela de hash é muito mais rápido do que filtrar uma coleção por valores de propriedade. Em vez de verificar todos os itens da coleção, o PowerShell pode verificar se a chave está definida e usar seu valor.
$Results = $Employees | ForEach-Object -Process {
$Email = $LookupHash[$_.Name].Email
[pscustomobject]@{
Id = $_.Id
Name = $_.Name
Email = $Email
}
}
Isso é muito mais rápido. Enquanto o filtro de looping levou minutos para ser concluído, a pesquisa de hash leva menos de um segundo.
Use Write-Host com cuidado
O comando Write-Host só deve ser usado quando você precisar escrever texto formatado no console do host, em vez de escrever objetos no pipeline Success.
Write-Host pode ser uma ordem de grandeza mais lenta do que [Console]::WriteLine() para hosts específicos, como pwsh.exe, powershell.exeou powershell_ise.exe. No entanto, não é garantido que [Console]::WriteLine() funcione em todos os anfitriões. Além disso, a saída escrita usando [Console]::WriteLine() não é gravada em transcrições iniciadas por Start-Transcript.
compilação JIT
O PowerShell compila o código de script para bytecode que é interpretado. A partir do PowerShell 3, para código executado repetidamente em um loop, o PowerShell pode melhorar o desempenho compilando o código em código nativo Just-in-time (JIT).
Loops com menos de 300 instruções são elegíveis para compilação JIT. Loops maiores do que isso são muito caros para compilar. Quando o loop é executado 16 vezes, o script é compilado em JIT em segundo plano. Quando a compilação JIT é concluída, a execução é transferida para o código compilado.
Evite chamadas repetidas para uma função
Chamar uma função pode ser uma operação cara. Se você estiver chamando uma função em um loop apertado de longa duração, considere mover o loop dentro da função.
Considere os seguintes exemplos:
$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'
}
}
}
O exemplo de 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
Evite encapsular pipelines de cmdlet
A maioria dos cmdlets é implementada para o pipeline, que é uma sintaxe e um processo sequenciais. Por exemplo:
cmdlet1 | cmdlet2 | cmdlet3
Inicializar um novo pipeline pode ser caro, portanto, você deve evitar envolver um pipeline de cmdlet em outro pipeline existente.
Considere o exemplo a seguir. O ficheiro Input.csv contém 2100 linhas. O comando Export-Csv é encapsulado dentro do pipeline de ForEach-Object. O cmdlet Export-Csv é invocado para cada iteração do loop de 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
Para o próximo exemplo, o comando Export-Csv foi movido para fora do pipeline de ForEach-Object.
Nesse caso, Export-Csv é invocado apenas uma vez, mas ainda processa todos os objetos passados 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
O exemplo desembrulhado é 372 vezes mais rápido. Além disso, observe que a primeira implementação requer o parâmetro Append, que não é necessário para a implementação posterior.
Evitar listagem desnecessária de coleções
Os operadores de comparação do PowerShell têm um recurso de conviência ao comparar coleções. Quando o valor do lado esquerdo na expressão é uma coleção, o operador retorna os elementos da coleção que correspondem ao valor do lado direito da expressão.
Esse recurso fornece uma maneira simples de filtrar uma coleção. Por exemplo:
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
No entanto, quando você usa uma comparação de coleção em uma instrução condicional que espera apenas um resultado booleano , esse recurso pode resultar em desempenho insatisfatório.
Tomemos como exemplo:
if ($Collection -like '*1*') { 'Found' }
Neste exemplo, o PowerShell compara o valor do lado direito com cada valor na coleção e retorna uma coleção de resultados. Como o resultado não está vazio, o resultado não nulo é avaliado como $true. A condição é verdadeira quando a primeira correspondência é identificada, mas o PowerShell ainda enumera toda a coleção. Essa enumeração pode ter um impacto significativo no desempenho de grandes coleções.
Uma maneira de melhorar o desempenho é usar o método Where() da coleção. O Where() método para de avaliar a coleção depois de encontrar a primeira correspondência.
# 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
Para um milhão de itens, usar o Where() método é significativamente mais rápido.
Criação de objetos
A criação de objetos usando o cmdlet New-Object pode ser lenta. O código a seguir compara o desempenho da criação de objetos usando o cmdlet New-Object com o acelerador de tipo [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
O PowerShell 5.0 adicionou o método estático new() para todos os tipos .NET. O código a seguir compara o desempenho da criação de objetos usando o cmdlet New-Object com o método 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
Use OrderedDictionary para criar novos objetos dinamicamente
Há situações em que talvez precisemos criar dinamicamente objetos com base em alguma entrada, a maneira talvez mais comumente usada para criar um novo PSObject e, em seguida, adicionar novas propriedades usando o cmdlet Add-Member. O custo de desempenho para pequenas coleções usando esta técnica pode ser insignificante, no entanto, pode se tornar muito percetível para grandes coleções. Nesse caso, a abordagem recomendada é usar um [OrderedDictionary] e, em seguida, convertê-lo em um PSObject usando o acelerador de tipo [pscustomobject]. Para obter mais informações, consulte a seção Criando dicionários ordenados do about_Hash_Tables.
Suponha que você tenha a seguinte resposta da API armazenada na variável $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" ]
]
}
]
}
Agora, suponha que você queira exportar esses dados para um CSV. Primeiro, você precisa criar novos objetos e adicionar as propriedades e os valores usando o 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
}
Usando um OrderedDictionary, o código pode ser traduzido para:
$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
}
Em ambos os casos, a saída $result seria a mesma:
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
Esta última abordagem torna-se exponencialmente mais eficiente à medida que o número de objetos e propriedades de membros aumenta.
Aqui está uma comparação de desempenho de três técnicas para criar objetos com 5 propriedades:
$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 estes são os resultados:
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