Почему рабочий процесс 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)
Другие вопросы по тегам