Оптимизация работы со строками в Powershell

в 13:44, , рубрики: powershell

Вводная: с данной заметке описывается как получить ускорение в 5-10 (и более раз) при обработке большого количества строк используя вместо String объект StringBuilder.

Вызов конструктора System.Text.StringBuilder:

$SomeString = New-Object System.Text.StringBuilder

Обратное преобразование в String:

$Result = $Str.ToString()


Во время написании скрипта, обрабатывающего много текстовых файлов, была обнаружена особенность работы со строками в powershell, а именно — значительно снижается скорость парсинга, если пытаться обработать строки при помощи стандартного объекта string.

Исходные данные — файл забитый строками по типу:


key;888;0xA9498353,888_FilialName

В сырой версии скрипта для контроля обработки применялись промежуточные текстовые файлы, потери времени на обработку файла в 1000 строк — 24 секунды, при увеличении размера файла задержка быстро растет. Пример:

function test 
    {
    $Path = 'C:Powershelltesttest.txt'

    $PSGF = Get-Content $Path

    # создаем файл
    $PSGFFileName = $Path + '-compare.txt'
    Remove-Item -Path $PSGFFileName -ErrorAction SilentlyContinue | Out-Null
    New-Item $PSGFFileName -Type File -ErrorAction SilentlyContinue | Out-Null

    # ToDo
    # в этом блоке теряется время, надо оптимизировать.
    # не использовать промежуточный файл Add-Content, потери на нем
    foreach ($Key in $PSGF)
    {
        $Val = $Key.ToString().Split(';')
        $test = $val[2]
        $Val = $test.ToString().Split(',')
        $test = $Val[0]
        Add-Content $PSGFFileName -Value $Test
    }

    $Result = Get-Content $PSGFFileName
    Remove-Item -Path $PSGFFileName -ErrorAction SilentlyContinue | Out-Null
    ### не оптимизированный код # end ################################
    return $Result
    }

Результат прогона:

99 строк — 1,8 секунды
1000 строк — 24,4 секунды
2000 строк — 66,17 секунды

Оптимизация №1

Ясно, что это никуда не годится. Заменяем выгрузку в файл операциями в памяти:

function test 
    {
    $Path = 'C:Powershelltesttest.txt'

    $PSGF = Get-Content $Path
    $Result = ''

    # 
    foreach ($Key in $PSGF)
    {
        $Val = $Key.ToString().Split(';')
        $test = $val[2]
        $Val = $test.ToString().Split(',')
        $test = $Val[0]
        $Result = $Result + "$test`r`n"
    }

    return $Result
    }

Measure-Command {  test }

Результат прогона:

99 строк — 0.0037 секунды
1000 строк — 0.055 секунды
2000 строк — 0.190 секунды

Вроде бы все хорошо, ускорение получено, но давайте посмотрим что происходит если строк в объекте больше:

10000 строк — 1,92 секунды
20000 строк — 8,07 секунды
40000 строк — 26,01 секунд

Такой метод обработки подходит для списков не более чем 5-8 тысяч строк, после начинаются потери на конструкторе объекта, менеджер памяти постоянно выделяет новую память при добавлении строки и копирует объект.

Оптимизация №2

Попробуем сделать лучше, используем «программистский» подход:

function test 
    {
    $Path = 'C:Powershelltesttest.txt'

    $PSGF = Get-Content $Path

    # берем объект из дотнета
    $Str = New-Object System.Text.StringBuilder

    foreach ($Key in $PSGF)
    {
        $Val = $Key.ToString().Split(';')
        $Val = $val[2].ToString().Split(',')
        $Val = $temp
        $temp = $Str.Append( "$Val`r`n" )
    }

    $Result = $Str.ToString()
    }

Measure-Command {  test }

Результат прогона: 40000 строк — 1,8 секунды.

Дальнейшие улучшения типа замены foreach на for, выбрасывание внутренней переменной $test не дали значимого прироста скорости.

Кратко:

Для эффективной работы с большим количеством строк используйте объект System.Text.StringBuilder. Вызов конструктора:

$SomeString = New-Object System.Text.StringBuilder

Преобразование в строку:

$Result = $Str.ToString()

Объяснение работы StringBuilder (весь секрет в более эффективной работе менеджера памяти).

Автор: pak-nikolai

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js