Главная > Powershell > Использование оператора += в Powershell

Использование оператора += в Powershell

Когда-то я уже давал один совет как ускорить работу с Powershell. Недавно попалась на глаза ещё одна хорошая статья на эту тему, которой захотелось поделиться.

Многие начинающие (и не только) скриптописатели стараются выполнить какую-либо операцию таким способом, чтобы код выглядел как можно проще и занимал как можно меньше строк, потому что в таком виде его легче потом разбирать и он “не такой страшный”. Всё логично, но есть одно “но”: просто записанный код не всегда самый производительный.

Например, возьмём код:

$OutputString = ""
$Array = @()

for ($i = 0; $i -lt 10; $i++)
{
    $OutputString += "Строка $i`r`n"
    $Array += "Элемент массива $i"
}

В этом примере в цикле к строке $OutputString дописывается подстрока, а к массиву $Array добавляется элемент.

Когда цикл выполняется небольшое количество раз такой вариант вполне подходит. При изменении количества циклов в пределах 1000 этот код выполняется меньше 100 мс. Если же выполнить цикл 10000 раз, он отработает за более чем 5 секунд, и с увеличением числа проходов циклов производительность становится всё хуже и хуже: 12,5 сек для 15000 проходов цикла, 26 сек для 20000 проходов и т.д. Время выполнения этого блока увеличивается не линейно, а по экспоненте. Другими словами, этот код очень плохо масштабируется.

Причина этого заключается в том, что каждый раз при выполнении оператора +=  создаётся новая строка (или массив), в неё (него) копируется содержимое оригинала, к нему добавляется подстрока (или элемент массива), после чего оригинал удаляется. Так как размер строки (или массива) увеличивается, каждая новая операция занимает всё больше и больше времени.

.NET Framework предлагает классы для решения проблем с производительностью как в случае строк, так и в случае массивов. Вместо добавления к строкам напрямую используется класс System.Text.StringBuilder. Для операций с массивами можно использовать System.Collections.ArrayList, или System.Collections.Generic.List (разница несущественна, в следующем примере будем использовать последний. В этом случае требуется указать тип элементов, которые будут добавляться в список, но это будет лучше, чем использование ArrayList в некоторых ситуациях.).

Измерения скорости выполнения команд выполняются при помощи командлета Measure-Command (о котором я как-то уже упоминал).

Для начала разберёмся со строками.

При использовании оператора += добавление строки выглядит следующим образом:

$OutputString = ""

Measure-Command {
    for ($i = 0; $i -lt 20000; $i++)
    {
        $OutputString += "Строка $i`r`n"
    }
}

(На всякий случай, напомню, что `r, `n – это Escape-последовательности.)

При использовании StringBuilder эта же операция выглядит так:

$StringBuilder = New-Object System.Text.StringBuilder

Measure-Command {
    for ($i = 0; $i -lt 20000; $i++)
    {
        $null = $StringBuilder.Append("Строка $i`r`n")
    }
}

# Преобразование StringBuilder в строку
$OutputString = $StringBuilder.ToString()

На первый взгляд первый вариант кажется проще. НО!…

Время выполнения первой команды – около 22 716 мс, второй – 620 мс. Если выполнить эти два блока не 20 000 раз, а 40 000 то первый код выполняется около 81,4 с, второй – 1,21 с. А когда я сдуру ради интереса поставил выполняться эти блоки по 200 000 раз, то второй вариант выполнился приблизительно за 5 с., в то время как первый я прождал минут 5, после чего моё терпение лопнуло и я прервал его выполнение.

Переходим к массивам.

При использовании оператора += код выглядит следующим образом:

$Array = @()

Measure-Command {
    for ($i = 0; $i -lt 20000; $i++)
    {
        $Array += "Элемент массива $i"
    }
}

При использовании System.Collections.Generic.List:

$List = New-Object System.Collections.Generic.List[System.String]

Measure-Command {
    for ($i = 0; $i -lt 20000; $i++)
    {
        $List.Add("Элемент массива $i")
    }
}

# Преобразование StringBuilder в строку
$Array = $list.ToArray()

Первый вариант выполняется приблизительно 42 278 мс, второй – 656 мс. При увеличении количества циклов ситуация со строками повторяется. Так что имейте в виду, что “проще – не всегда лучше”.

Реклама
Рубрики:Powershell Метки: ,
  1. Комментариев нет.
  1. No trackbacks yet.

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s

%d такие блоггеры, как: