Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Program PowerShell oferuje kilka opcji tworzenia wywołań równoległych.
-
Start-Joburuchamia każde zadanie w osobnym procesie, w którym uruchamiane jest nowe wystąpienie programu PowerShell. W wielu przypadkach pętla liniowa jest szybsza. Ponadto serializacja i deserializacja mogą ograniczyć użyteczność zwracanych obiektów. To polecenie jest wbudowane we wszystkich wersjach programu PowerShell. -
Start-ThreadJobto polecenie cmdlet, które znajduje się w module ThreadJob. To polecenie używa obszarów runspace programu PowerShell do tworzenia zadań opartych na wątkach i zarządzania nimi. Te zadania są lżejsze niż zadania utworzone przezStart-Jobi unikają potencjalnej utraty wierności typów wymaganej przez serializację i deserializację między procesami. Moduł ThreadJob jest dostarczany z programem PowerShell 7 lub nowszym. W przypadku programu Windows PowerShell 5.1 możesz zainstalować ten moduł z galerii programu PowerShell. - Użyj przestrzeni nazw System.Management.Automation.Runspaces z zestawu POWERShell SDK, aby utworzyć własną logikę równoległą. Zarówno
ForEach-Object -Parallel, jak iStart-ThreadJobużywają przestrzeni uruchomieniowych PowerShell do równoległego wykonywania kodu. - Przepływy pracy to funkcja programu Windows PowerShell 5.1. Przepływy pracy nie są dostępne w programie PowerShell 7.0 lub nowszym. Przepływy pracy to specjalny typ skryptu programu PowerShell, który może być uruchamiany równolegle. Są one przeznaczone dla długotrwałych zadań i można je wstrzymywać i wznawiać. Przepływy pracy nie są zalecane w przypadku nowego programowania. Aby uzyskać więcej informacji, zobacz about_Workflows.
-
ForEach-Object -Paralleljest funkcją programu PowerShell w wersji 7.0 lub nowszej. Podobnie jakStart-ThreadJobprogram , używa przestrzeni uruchomieniowych programu PowerShell do tworzenia zadań opartych na wątkach i zarządzania nimi. To polecenie jest przeznaczone do użycia w potoku.
Ograniczanie współbieżności wykonywania
Równoległe uruchamianie skryptów nie gwarantuje lepszej wydajności. Na przykład następujące scenariusze mogą korzystać z równoległego wykonywania:
- Skrypty intensywnie korzystające z obliczeń na wielowątowych procesorach wielordzeniowych
- Skrypty, które poświęcają czas na oczekiwanie na wyniki lub wykonywanie operacji na plikach, o ile te operacje nie blokują się nawzajem.
Ważne jest, aby zrównoważyć obciążenie związane z wykonywaniem równoległym z rodzajem wykonywanej pracy. Istnieją również ograniczenia liczby wywołań, które mogą być uruchamiane równolegle.
Polecenia Start-ThreadJob i ForEach-Object -Parallel mają parametr ThrottleLimit , aby ograniczyć liczbę uruchomionych zadań jednocześnie. W miarę uruchamiania większej liczby zadań są one kolejkowane i czekają, aż bieżąca liczba zadań spadnie poniżej limitu ograniczenia. Od programu PowerShell 7.1 ForEach-Object -Parallel domyślnie ponownie używa przestrzeni uruchomieniowych z puli obszarów runspace. Parametr ThrottleLimit ustawia rozmiar puli przestrzeni uruchomieniowej. Domyślny rozmiar puli przestrzeni uruchomieniowej to 5. Nadal można utworzyć nową przestrzeń runspace dla każdej iteracji przy użyciu przełącznika UseNewRunspace.
Polecenie Start-Job nie ma parametru ThrottleLimit . Musisz zarządzać liczbą jednocześnie uruchomionych zadań.
Mierzenie wydajności
Następująca funkcja, Measure-Parallel, porównuje szybkość następujących metod wykonywania równoległego:
Start-Job— tworzy proces podrzędny programu PowerShell działający w tleStart-ThreadJob— uruchamia każde zadanie w osobnym wątkuForEach-Object -Parallel— uruchamia każde zadanie w osobnym wątkuStart-Process- wywołuje program zewnętrzny asynchronicznieUwaga / Notatka
Takie podejście ma sens tylko wtedy, gdy zadania równoległe składają się tylko z jednego wywołania programu zewnętrznego, w przeciwieństwie do uruchamiania bloku kodu programu PowerShell. Jedynym sposobem przechwytywania danych wyjściowych za pomocą tego podejścia jest przekierowanie do pliku.
function Measure-Parallel {
[CmdletBinding()]
param(
[ValidateRange(2, 2147483647)]
[int] $BatchSize = 5,
[ValidateSet('Job', 'ThreadJob', 'Process', 'ForEachParallel', 'All')]
[string[]] $Approach,
# pass a higher count to run multiple batches
[ValidateRange(2, 2147483647)]
[int] $JobCount = $BatchSize
)
$noForEachParallel = $PSVersionTable.PSVersion.Major -lt 7
$noStartThreadJob = -not (Get-Command -ErrorAction Ignore Start-ThreadJob)
# Translate the approach arguments into their corresponding hashtable keys (see below).
if ('All' -eq $Approach) { $Approach = 'Job', 'ThreadJob', 'Process', 'ForEachParallel' }
$approaches = $Approach.ForEach({
if ($_ -eq 'ForEachParallel') { 'ForEach-Object -Parallel' }
else { $_ -replace '^', 'Start-' }
})
if ($noStartThreadJob) {
if ($interactive -or $approaches -contains 'Start-ThreadJob') {
Write-Warning "Start-ThreadJob is not installed, omitting its test."
$approaches = $approaches.Where({ $_ -ne 'Start-ThreadJob' })
}
}
if ($noForEachParallel) {
if ($interactive -or $approaches -contains 'ForEach-Object -Parallel') {
Write-Warning 'ForEach-Object -Parallel require PowerShell v7+, omitting its test.'
$approaches = $approaches.Where({ $_ -ne 'ForEach-Object -Parallel' })
}
}
# Simulated input: Create 'f0.zip', 'f1'.zip', ... file names.
$zipFiles = 0..($JobCount - 1) -replace '^', 'f' -replace '$', '.zip'
# Sample executables to run - here, the native shell is called to simply
# echo the argument given.
$exe = if ($env:OS -eq 'Windows_NT') { 'cmd.exe' } else { 'sh' }
# The list of its arguments *as a single string* - use '{0}' as the placeholder
# for where the input object should go.
$exeArgList = if ($env:OS -eq 'Windows_NT') {
'/c "echo {0} > NUL:"'
} else {
'-c "echo {0} > /dev/null"'
}
# A hashtable with script blocks that implement the 3 approaches to parallelism.
$approachImpl = [ordered] @{}
# child-process-based job
$approachImpl['Start-Job'] = {
param([array] $batch)
$batch |
ForEach-Object {
Start-Job {
Invoke-Expression ($using:exe + ' ' + ($using:exeArgList -f $args[0]))
} -ArgumentList $_
} |
Receive-Job -Wait -AutoRemoveJob | Out-Null
}
# thread-based job - requires the ThreadJob module
if (-not $noStartThreadJob) {
# If Start-ThreadJob is available, add an approach for it.
$approachImpl['Start-ThreadJob'] = {
param([array] $batch)
$batch |
ForEach-Object {
Start-ThreadJob -ThrottleLimit $BatchSize {
Invoke-Expression ($using:exe + ' ' + ($using:exeArgList -f $args[0]))
} -ArgumentList $_
} |
Receive-Job -Wait -AutoRemoveJob | Out-Null
}
}
# ForEach-Object -Parallel job
if (-not $noForEachParallel) {
$approachImpl['ForEach-Object -Parallel'] = {
param([array] $batch)
$batch | ForEach-Object -ThrottleLimit $BatchSize -Parallel {
Invoke-Expression ($using:exe + ' ' + ($using:exeArgList -f $_))
}
}
}
# direct execution of an external program
$approachImpl['Start-Process'] = {
param([array] $batch)
$batch |
ForEach-Object {
Start-Process -NoNewWindow -PassThru $exe -ArgumentList ($exeArgList -f $_)
} |
Wait-Process
}
# Partition the array of all indices into subarrays (batches)
$batches = @(
0..([math]::Ceiling($zipFiles.Count / $batchSize) - 1) | ForEach-Object {
, $zipFiles[($_ * $batchSize)..($_ * $batchSize + $batchSize - 1)]
}
)
$tsTotals = foreach ($appr in $approaches) {
$i = 0
$tsTotal = [timespan] 0
$batches | ForEach-Object {
Write-Verbose "$batchSize-element '$appr' batch"
$ts = Measure-Command { & $approachImpl[$appr] $_ | Out-Null }
$tsTotal += $ts
if (++$i -eq $batches.Count) {
# last batch processed.
if ($batches.Count -gt 1) {
Write-Verbose ("'$appr' processing $JobCount items finished in " +
"$($tsTotal.TotalSeconds.ToString('N2')) secs.")
}
$tsTotal # output the overall timing for this approach
}
}
}
# Output a result object with the overall timings.
$oht = [ordered] @{}
$oht['JobCount'] = $JobCount
$oht['BatchSize'] = $BatchSize
$oht['BatchCount'] = $batches.Count
$i = 0
foreach ($appr in $approaches) {
$oht[($appr + ' (secs.)')] = $tsTotals[$i++].TotalSeconds.ToString('N2')
}
[pscustomobject] $oht
}
W poniższym przykładzie użyto Measure-Parallel metody do równoległego uruchamiania 20 zadań( 5 naraz przy użyciu wszystkich dostępnych metod).
Measure-Parallel -Approach All -BatchSize 5 -JobCount 20 -Verbose
Następujące dane wyjściowe pochodzą z komputera z systemem Windows z uruchomionym programem PowerShell 7.5.1. Czas może się różnić w zależności od wielu czynników, ale proporcje powinny zapewnić poczucie względnej wydajności.
VERBOSE: 5-element 'Start-Job' batch
VERBOSE: 5-element 'Start-Job' batch
VERBOSE: 5-element 'Start-Job' batch
VERBOSE: 5-element 'Start-Job' batch
VERBOSE: 'Start-Job' processing 20 items finished in 7.58 secs.
VERBOSE: 5-element 'Start-ThreadJob' batch
VERBOSE: 5-element 'Start-ThreadJob' batch
VERBOSE: 5-element 'Start-ThreadJob' batch
VERBOSE: 5-element 'Start-ThreadJob' batch
VERBOSE: 'Start-ThreadJob' processing 20 items finished in 2.37 secs.
VERBOSE: 5-element 'Start-Process' batch
VERBOSE: 5-element 'Start-Process' batch
VERBOSE: 5-element 'Start-Process' batch
VERBOSE: 5-element 'Start-Process' batch
VERBOSE: 'Start-Process' processing 20 items finished in 0.26 secs.
VERBOSE: 5-element 'ForEach-Object -Parallel' batch
VERBOSE: 5-element 'ForEach-Object -Parallel' batch
VERBOSE: 5-element 'ForEach-Object -Parallel' batch
VERBOSE: 5-element 'ForEach-Object -Parallel' batch
VERBOSE: 'ForEach-Object -Parallel' processing 20 items finished in 0.79 secs.
JobCount : 20
BatchSize : 5
BatchCount : 4
Start-Job (secs.) : 7.58
Start-ThreadJob (secs.) : 2.37
Start-Process (secs.) : 0.26
ForEach-Object -Parallel (secs.) : 0.79
Wnioski
- Podejście
Start-Processdziała najlepiej, ponieważ nie ma obciążenia związanego z zarządzaniem zadaniami. Jednak jak wspomniano wcześniej, takie podejście ma podstawowe ograniczenia. - Element
ForEach-Object -Paralleldodaje najmniejsze obciążenie, a następnieStart-ThreadJob. -
Start-Jobma największe obciążenie z powodu ukrytych wystąpień programu PowerShell tworzonych dla każdego zadania.
Podziękowania
Wiele informacji w tym artykule opiera się na odpowiedziach Santiago Squarzon i mklement0 w tym poście na Stack Overflow.
Możesz również zainteresować się modułem PSParallelPipeline utworzonym przez Santiago Squarzon.