Почему рабочий процесс PowerShell значительно медленнее, чем сценарий без рабочего процесса для анализа файлов XML
Я пишу программу PowerShell для анализа содержимого более 1900+ больших файлов конфигурации XML (50000+ строк, 1,5 МБ). Просто для теста я перенесу 36 тестовых файлов на свой компьютер (Win 10; PS 5.1; 32 ГБ ОЗУ) и напишу быстрый скрипт для проверки скорости выполнения.
$TestDir = "E:\Powershell\Test"
$TestXMLs = Get-ChildItem $TestDir -Recurse -Include *.xml
foreach ($TestXML in $TestXMLs)
{
[xml]$XML = Get-Content $TestXML
(($XML.root.servers.server).Where{$_.name -eq "Server1"}).serverid
}
Это длится от 36 до 40 секунд. Я сделал несколько тестов с помощью measure-command.
Затем я попробовал рабочий процесс с foreach -paralell, предполагая, что параллельная загрузка нескольких файлов ускорит процесс.
Workflow Test-WF
{
$TestDir = "E:\Powershell\Test"
$TestXMLs = Get-ChildItem $TestDir -Recurse -Include *.xml
foreach -parallel -throttle 10 ($TestXML in $TestXMLs)
{
[xml]$XML = Get-Content $TestXML
(($TestXML.root.servers.server).Where{$_.name -eq "Sevrver1"}).serverid
}
}
Test-WF #execute workflow
Сценарию с рабочим процессом требуется от 118 до 132 секунд.
Теперь мне просто интересно, что может быть причиной того, что рабочий процесс работает намного медленнее? Перекомпилирование в XMAL, может быть, или более медленный алгоритм загрузки файлов XML в WWF?
1 ответ
foreach -parallel
На сегодняшний день это самый медленный вариант распараллеливания, который вы имеете с PowerShell, поскольку рабочие процессы не рассчитаны на скорость, а предназначены для длительных операций, которые можно безопасно прервать и возобновить.
Реализация этих механизмов безопасности вносит некоторые накладные расходы, поэтому ваш скрипт работает медленнее, чем рабочий процесс.
Если вы хотите оптимизировать скорость выполнения, используйте вместо этого пространства выполнения:
$TestDir = "E:\Powershell\Test"
$TestXMLs = Get-ChildItem $TestDir -Recurse -Include *.xml
# Set up runspace pool
$RunspacePool = [runspacefactory]::CreateRunspacePool(1,10)
$RunspacePool.Open()
# Assign new jobs/runspaces to a variable
$Runspaces = foreach ($TestXML in $TestXMLs)
{
# Create new PowerShell instance to hold the code to execute, add arguments
$PSInstance = [powershell]::Create().AddScript({
param($XMLPath)
[xml]$XML = Get-Content $XMLPath
(($XML.root.servers.server).Where{$_.name -eq "Server1"}).serverid
}).AddParameter('XMLPath', $TestXML.FullName)
# Assing PowerShell instance to RunspacePool
$PSInstance.RunspacePool = $RunspacePool
# Start executing asynchronously, keep instance + IAsyncResult objects
New-Object psobject -Property @{
Instance = $PSInstance
IAResult = $PSInstance.BeginInvoke()
Argument = $TestXML
}
}
# Wait for the the runspace jobs to complete
while($Runspaces |Where-Object{-not $_.IAResult.IsCompleted})
{
Start-Sleep -Milliseconds 500
}
# Collect the results
$Results = $Runspaces |ForEach-Object {
$Output = $_.Instance.EndInvoke($_.IAResult)
New-Object psobject -Property @{
File = $TestXML
ServerID = $Output
}
}
Советы по быстрой обработке XML-бонусов:
Как рекомендует wOxxOm, используя Xml.Load()
это намного быстрее, чем с помощью Get-Content
читать в документе XML.
Кроме того, используя точечную запись ($xml.root.servers.server
) и Where({})
Метод расширения также будет мучительно медленным, если есть много servers
или же server
узлы. Использовать SelectNodes()
метод с выражением XPath для поиска "Server1" вместо этого (учтите, что XPath чувствителен к регистру):
$PSInstance = [powershell]::Create().AddScript({
param($XMLPath)
$XML = New-Object Xml
$XML.Load($XMLPath)
$Server1Node = $XML.SelectNodes('/root/servers/server[@name = "Server1"]')
return $Server1Node.serverid
}).AddParameter('XMLPath', $TestXML.FullName)