Оптимизация работы со строками в 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:\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(';')         $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 (весь секрет в более эффективной работе менеджера памяти).

ссылка на оригинал статьи http://habrahabr.ru/post/273619/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *