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

Джерело: Хабрахабр
  • avatar
  • 0

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.