Доповнити попередньо існуючі корисні відповіді керівництвом, коли використовувати який підхід та порівняння ефективності .
Поза трубопроводу використовуйте (PSv3 +):
$ об’єктів . Ім'я
як показано у відповіді rageandqq , яка є і синтаксично простішою, і значно швидшою .
У конвеєрі, де результат потрібно додатково обробляти або результати не вміщуються в пам'ять в цілому, використовуйте:
$ об'єктів | Select-Object -ExpandProperty Name
- Необхідність
-ExpandProperty
пояснюється у відповіді Скотта Саада .
- Ви отримуєте звичайні конвеєрні переваги обробки однієї за одною, яка, як правило, дає результат відразу та підтримує постійне використання пам'яті (якщо ви все одно не збираєте результати в пам'яті).
- Компроміс :
- Використання трубопроводу порівняно повільне .
Для невеликих колекцій вхідних даних (масивів) ви, мабуть, не помітите різниці , і, особливо в командному рядку, важливіше іноді можливість легко вводити команду.
Ось проста альтернатива , яка, проте, є найповільнішим підходом ; він використовує спрощений ForEach-Object
синтаксис, який називається оператором операції (знову ж таки, PSv3 +):; наприклад, наступне рішення PSv3 + легко додати до існуючої команди:
$objects | % Name # short for: $objects | ForEach-Object -Process { $_.Name }
Для повноти: Маловідомий .ForEach()
метод масиву PSv4 + , більш всебічний, який розглядається в цій статті , є ще однією альтернативою :
# By property name (string):
$objects.ForEach('Name')
# By script block (more flexibility; like ForEach-Object)
$objects.ForEach({ $_.Name })
Цей підхід схожий на перерахування членів , з однаковими компромісами, за винятком того, що логіка трубопроводу не застосовується; це незначно повільніше , хоча все ж помітно швидше, ніж трубопровід.
Для вилучення одного значення властивості за іменем ( аргумент рядка ) це рішення нарівні з перерахуванням членів (хоча останній синтаксично простіший).
Скрипт-блок - варіант , дозволяє довільні перетворення ; це швидше - все в пам'яті відразу - альтернатива ForEach-Object
конвеєру cmdlet ( %
) .
Порівнюючи ефективність різних підходів
Ось зразки часу для різних підходів, засновані на вхідній колекції 10,000
об'єктів , усередненій протягом 10 циклів; абсолютні цифри не важливі і змінюються залежно від багатьох факторів, але це повинно створювати відчуття відносної продуктивності (терміни виходять з одноядерної Windows 10 VM:
Важливо
Відносна продуктивність змінюється залежно від того, вхідні об'єкти є примірниками звичайних типів .NET (наприклад, як вихідними Get-ChildItem
) або [pscustomobject]
екземплярами (наприклад, як вихідними Convert-FromCsv
).
Причина полягає в тому, що [pscustomobject]
властивостями динамічно керує PowerShell, і він може отримати доступ до них швидше, ніж до звичайних властивостей (статично визначеного) регулярного типу .NET. Обидва сценарії описані нижче.
Тести використовують як вхідні дані колекції, що вже заповнені пам'яттю, щоб зосередити увагу на чистому виконанні функції вилучення властивостей. Якщо вхідний виклик командлета / функції в якості вхідного сигналу, різниця в продуктивності, як правило, буде набагато менш вираженою, оскільки час, проведений всередині цього виклику, може становити більшу частину витраченого часу.
Для стислості псевдонім %
використовується для ForEach-Object
командлета.
Загальні висновки , застосовні як до звичайного типу .NET, так і до [pscustomobject]
вхідного:
Перерахування членів ( $collection.Name
) та foreach ($obj in $collection)
рішення - це найшвидше , в 10 разів або швидше, ніж найшвидше рішення на основі конвеєра.
Дивно, але % Name
працює набагато гірше % { $_.Name }
- дивіться цю проблему GitHub .
Тут PowerShell Core постійно перевершує Windows Powershell.
Часи з регулярними типами .NET :
- PowerShell Core v7.0.0-превью.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.005
1.06 foreach($o in $objects) { $o.Name } 0.005
6.25 $objects.ForEach('Name') 0.028
10.22 $objects.ForEach({ $_.Name }) 0.046
17.52 $objects | % { $_.Name } 0.079
30.97 $objects | Select-Object -ExpandProperty Name 0.140
32.76 $objects | % Name 0.148
- Windows PowerShell v5.1.18362.145
Comparing property-value extraction methods with 10000 input objects, averaged over 10 runs...
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.012
1.32 foreach($o in $objects) { $o.Name } 0.015
9.07 $objects.ForEach({ $_.Name }) 0.105
10.30 $objects.ForEach('Name') 0.119
12.70 $objects | % { $_.Name } 0.147
27.04 $objects | % Name 0.312
29.70 $objects | Select-Object -ExpandProperty Name 0.343
Висновки:
- У PowerShell Ядра ,
.ForEach('Name')
явно перевершує .ForEach({ $_.Name })
. У Windows PowerShell цікаво, що останній швидше, хоча й лише незначно.
Часи з [pscustomobject]
екземплярами :
- PowerShell Core v7.0.0-превью.3
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.006
1.11 foreach($o in $objects) { $o.Name } 0.007
1.52 $objects.ForEach('Name') 0.009
6.11 $objects.ForEach({ $_.Name }) 0.038
9.47 $objects | Select-Object -ExpandProperty Name 0.058
10.29 $objects | % { $_.Name } 0.063
29.77 $objects | % Name 0.184
- Windows PowerShell v5.1.18362.145
Factor Command Secs (10-run avg.)
------ ------- ------------------
1.00 $objects.Name 0.008
1.14 foreach($o in $objects) { $o.Name } 0.009
1.76 $objects.ForEach('Name') 0.015
10.36 $objects | Select-Object -ExpandProperty Name 0.085
11.18 $objects.ForEach({ $_.Name }) 0.092
16.79 $objects | % { $_.Name } 0.138
61.14 $objects | % Name 0.503
Висновки:
Зверніть увагу , як при [pscustomobject]
вході .ForEach('Name')
на сьогоднішній день перевищує скрипт-блок на основі варіанту, .ForEach({ $_.Name })
.
Аналогічно, [pscustomobject]
введення робить на основі конвеєра Select-Object -ExpandProperty Name
швидше, в Windows PowerShell практично нарівні .ForEach({ $_.Name })
, але в PowerShell Core ще приблизно на 50% повільніше.
Якщо коротко: за випадковим винятком % Name
, за [pscustomobject]
допомогою рядкових методів посилання на властивості перевершують ті, що базуються на сценарії.
Вихідний код для тестів :
Примітка:
$count = 1e4 # max. input object count == 10,000
$runs = 10 # number of runs to average
# Note: Using [pscustomobject] instances rather than instances of
# regular .NET types changes the performance characteristics.
# Set this to $true to test with [pscustomobject] instances below.
$useCustomObjectInput = $false
# Create sample input objects.
if ($useCustomObjectInput) {
# Use [pscustomobject] instances.
$objects = 1..$count | % { [pscustomobject] @{ Name = "$foobar_$_"; Other1 = 1; Other2 = 2; Other3 = 3; Other4 = 4 } }
} else {
# Use instances of a regular .NET type.
# Note: The actual count of files and folders in your home dir. tree
# may be less than $count
$objects = Get-ChildItem -Recurse $HOME | Select-Object -First $count
}
Write-Host "Comparing property-value extraction methods with $($objects.Count) input objects, averaged over $runs runs..."
# An array of script blocks with the various approaches.
$approaches = { $objects | Select-Object -ExpandProperty Name },
{ $objects | % Name },
{ $objects | % { $_.Name } },
{ $objects.ForEach('Name') },
{ $objects.ForEach({ $_.Name }) },
{ $objects.Name },
{ foreach($o in $objects) { $o.Name } }
# Time the approaches and sort them by execution time (fastest first):
Time-Command $approaches -Count $runs | Select Factor, Command, Secs*
$results = @($objects | %{ $_.Name })
. Це може бути зручніше вводити в командному рядку часом, хоча я думаю , що відповідь Скотта , як правило, краща.