Вводная: с данной заметке описывается как получить ускорение в 5-10 (и более раз) при обработке большого количества строк используя вместо String объект StringBuilder.
Вызов конструктора System.Text.StringBuilder:
Обратное преобразование в String:
Во время написании скрипта, обрабатывающего много текстовых файлов, была обнаружена особенность работы со строками в powershell, а именно — значительно снижается скорость парсинга, если пытаться обработать строки при помощи стандартного объекта string.
Исходные данные — файл забитый строками по типу:
В сырой версии скрипта для контроля обработки применялись промежуточные текстовые файлы, потери времени на обработку файла в 1000 строк — 24 секунды, при увеличении размера файла задержка быстро растет. Пример:
Результат прогона:
99 строк — 1,8 секунды
1000 строк — 24,4 секунды
2000 строк — 66,17 секунды
Ясно, что это никуда не годится. Заменяем выгрузку в файл операциями в памяти:
Результат прогона:
99 строк — 0.0037 секунды
1000 строк — 0.055 секунды
2000 строк — 0.190 секунды
Вроде бы все хорошо, ускорение получено, но давайте посмотрим что происходит если строк в объекте больше:
10000 строк — 1,92 секунды
20000 строк — 8,07 секунды
40000 строк — 26,01 секунд
Такой метод обработки подходит для списков не более чем 5-8 тысяч строк, после начинаются потери на конструкторе объекта, менеджер памяти постоянно выделяет новую память при добавлении строки и копирует объект.
Попробуем сделать лучше, используем «программистский» подход:
Результат прогона: 40000 строк — 1,8 секунды.
Дальнейшие улучшения типа замены foreach на for, выбрасывание внутренней переменной $test не дали значимого прироста скорости.
Кратко:
Для эффективной работы с большим количеством строк используйте объект System.Text.StringBuilder. Вызов конструктора:
Преобразование в строку:
Объяснение работы 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:\Powershell\test\test.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:\Powershell\test\test.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:\Powershell\test\test.txt'
$PSGF = Get-Content $Path
# берем объект из дотнета
$Str = New-Object System.Text.StringBuilder
foreach ($Key in $PSGF)
{
$Val = $Key.ToString().Split(';')
$temp = $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 (весь секрет в более эффективной работе менеджера памяти).
Комментарии (3)
Dywar
22.12.2015 11:32Еще можно скачать Powergui, собрать из скрипта PE файл и запускать с запросом пароля или без.
И/или использовать C# напрямую:
PS C:\>$source = @" public class BasicTest { public static int Add(int a, int b) { return (a + b); } public int Multiply(int a, int b) { return (a * b); } } "@ PS C:\>Add-Type -TypeDefinition $source PS C:\>[BasicTest]::Add(4, 3) PS C:\>$basicTestObject = New-Object BasicTest PS C:\>$basicTestObject.Multiply(5, 2)
Протестировать и оптимизировать код поможет cshell или linqpad. Замерять скорость поможет Stopwatch.
WNeZRoS
Ещё для ускорения можно заменить Split на LastIndexOf, IndexOf, Substring. Опять же будет более эффективная работа с памятью: не будут выделяться в отдельные строки не нужные части.
pak-nikolai
я уже дальше копать не стал, т.к. не получил сколь нибудь значимого прироста скорости от оптимизации внутри цикла