Вводная: с данной заметке описывается как получить ускорение в 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