powershell runspace что это
Как запустить сценарии PowerShell параллельно без использования заданий?
Но я не хочу ждать, пока каждая PSSession подключится и вызовет команду. Как это можно сделать параллельно, без Джобса?
С параллельным выполнением Runspace:
Сокращение неизбежного времени ожидания
В исходном конкретном случае вызываемый исполняемый файл имеет /nowait параметр, который предотвращает блокировку вызывающего потока, пока задание (в данном случае повторная синхронизация времени) завершается само по себе.
Это значительно сокращает общее время выполнения с точки зрения эмитентов, но подключение к каждой машине все еще выполняется в последовательном порядке. Последовательное подключение к тысячам клиентов может занять много времени в зависимости от количества машин, которые по тем или иным причинам недоступны из-за накопления времени ожидания.
Чтобы обойти необходимость ставить в очередь все последующие соединения в случае одного или нескольких последовательных тайм-аутов, мы можем направить работу по подключению и вызову команд в отдельные пространства выполнения PowerShell, выполняя их параллельно.
Что такое Runspace?
Как и в случае с исходной проблемой, задачу вызова команд из нескольких пространств выполнения можно разбить на:
Шаблон RunspacePool
1. Создайте RunspacePool и Open() это:
2. Создайте экземпляр PowerShell, присоедините к нему некоторый исполняемый код и назначьте его нашему RunspacePool:
3. Асинхронно вызовите экземпляр PowerShell, используя APM:
Заворачивая его в RunspacePool
Используя вышеописанную технику, мы можем обернуть последовательные итерации создания новых соединений и вызова удаленной команды в параллельном потоке выполнения:
Предполагая, что ЦП способен выполнять все 8 пространств выполнения одновременно, мы должны увидеть, что время выполнения значительно сокращается, но за счет читабельности сценария из-за довольно «продвинутых» используемых методов.
Определение оптимальной степени параллизма:
Мы могли бы легко создать RunspacePool, который позволяет одновременно выполнять 100 пространств выполнения:
Но в конечном итоге все сводится к тому, сколько единиц исполнения может обрабатывать наш локальный процессор. Другими словами, до тех пор, пока выполняется ваш код, не имеет смысла разрешать большее количество пространств выполнения, чем у вас есть логические процессоры для отправки выполнения кода.
Благодаря WMI этот порог довольно легко определить:
Если, с другой стороны, код, который вы выполняете сам, требует много времени на ожидание из-за внешних факторов, таких как задержка в сети, вы все равно можете получить выгоду от запуска большего количества одновременных пространств выполнения, чем у логических процессоров, так что вы, вероятно, захотите протестировать диапазона возможных максимальных пространств выполнения, чтобы найти безубыточность :
Как построить ракетный ускоритель для скриптов PowerCLI
Два инструмента системного администратора VMware для оптимизации и ускорения скриптов.
Рано или поздно любой системный администратор VMware доходит до автоматизации рутинных задач. Начинается все с командной строки, потом идет PowerShell или VMware PowerCLI.
Допустим, вы освоили PowerShell чуть дальше запуска ISE и использования стандартных командлетов из модулей, которые работают за счет “какой-то магии”. Когда вы начнете считать виртуальные машины сотнями, то обнаружите, что скрипты, которые выручали на малых масштабах, работают заметно медленнее на больших.
В этой ситуации выручат 2 инструмента:
Дальше кратко расскажу про каждый инструмент и покажу примеры использования. Разберем конкретные скрипты и посмотрим, когда лучше работает один, когда второй. Поехали!
Первая ступень: Runspace
Итак, Runspace предназначен для параллельной обработки задач вне основного модуля. Конечно, можно запустить еще один процесс, который съест сколько-то памяти, процессора и т. д. Если ваш скрипт отрабатывает за пару минут и тратит гигабайт памяти, скорее всего, Runspace вам не потребуется. А вот для скриптов на десятки тысяч объектов он нужен.
Начать освоение можно отсюда:
Что дает использование Runspace:
Вот пример из интернета, когда Runspace помогает:
“Конкуренция за ресурсы хранилища – одна из метрик, которые сложно отслеживать в vSphere. Внутри vCenter нельзя просто взять и посмотреть, какая ВМ потребляет больше ресурсов хранилища. К счастью, собрать эти данные можно за минуты благодаря PowerShell.
Поделюсь скриптом, который позволит системным администраторам VMware быстро выполнять поиск по всему vCenter и получать лист ВМ с данными по их среднему потреблению.
Скрипт использует PowerShell runspaces, чтобы каждый хост ESXi собирал информацию по потреблению его собственных ВМ в отдельном Runspace и сразу сообщал о завершении. Это позволяет PowerShell сразу закрывать джобы, а не перебирать последовательно хосты и не ждать, пока каждый завершит свой запрос”.
В случае ниже Runspace уже не у дел:
“Пытаюсь написать скрипт, который собирает много данных с ВМ и при необходимости записывает новые данные. Проблема в том, что ВМ достаточно много, и на одну машину тратится по 5-8 секунд”.
Здесь уже понадобится Get-View, перейдем к нему.
Вторая ступень: Get-View
Чтобы разобраться, чем полезен Get-View, стоит вспомнить, как работают командлеты вообще.
Командлеты нужны для удобного получения информации без необходимости штудировать справочники по API и изобретать очередной велосипед. То, что в старые времена расписывалось в сотню-другую строк кода, PowerShell позволяет сделать одной командой. За это удобство мы платим скоростью. Внутри самих командлетов никакой магии нет: тот же скрипт, но более низкого уровня, написанный умелыми руками мастера из солнечной Индии.
Теперь для сравнения с Get-View возьмем командлет Get-VM: он обращается к виртуальной машине и возвращает составной объект, то есть прикладывает к нему другие сопутствующие объекты: VMHost, Datastore и т. д.
Get-View на его месте не прикручивает в возвращаемый объект ничего лишнего. Более того, он позволяет жестко указать, какая именно информация нам нужна, что облегчит объект на выходе. В Windows Server в целом и в Hyper-V, в частности, прямым аналогом является командлет Get-WMIObject – идея абсолютно та же.
Get-View неудобен в рутинных операциях над точечными объектами. Но когда речь заходит о тысячах и десятках тысяч объектов, ему нет цены.
Почитать подробнее можно в блоге VMware:
Сейчас все покажу на реальном кейсе.
Пишем скрипт для выгрузки ВМ
Однажды мой коллега попросил меня оптимизировать его скрипт.
Задача – обычная рутина: найти все ВМ с дублирующимся параметром cloud.uuid (да, такое возможно при клонировании ВМ в vCloud Director).
Очевидный вариант решения, который приходит на ум:
Исходным вариантом был такой незамысловатый скрипт:
Все предельно просто и понятно. Пишется за пару минут с перерывом на кофе. Прикрутить фильтрацию, и дело сделано.
2 минуты 47 секунд при обработке почти 10k ВМ. Бонусом – отсутствие фильтров и необходимость вручную сортировать результат. Очевидно, что скрипт просит оптимизации.
Ранспейсы первыми приходят на помощь, когда нужно единовременно получить метрики хостов с vCenter или требуется обработать десятки тысяч объектов. Посмотрим, что даст этот подход.
Включаем первую скорость: PowerShell Runspaces
Первое, что приходит на ум для этого скрипта: выполнить цикл не последовательно, а в параллельных потоках, собрать все данные в один объект и отфильтровать.
Но есть проблема: PowerCLI не позволит нам открывать множество независимых сессий к vCenter и выкинет веселую ошибку:
Теперь реализуем мультипоточность через Runspace Pools.
В итоге получаем скрипт:
Прелесть этого скрипта в том, что его можно использовать и в других похожих случаях, просто заменив ScriptBlock и параметры, которые будут переданы в поток. Exploit it!
55 секунд. Уже лучше, но все равно можно быстрее.
Переходим на вторую скорость: GetView
Выясняем, что не так.
Первое и очевидное: командлет Get-VM выполняется долго.
Второе: командлет Get-AdvancedOptions выполняется еще дольше.
Сначала разберемся со вторым.
Get-AdvancedOptions удобен на отдельных объектах ВМ, но очень неповоротлив при работе со множеством объектов. Ту же самую информацию мы можем получить из самого объекта виртуальной машины (Get-VM). Просто она хорошо зарыта в объекте ExtensionData. Вооружившись фильтрацией, ускоряем процесс получения нужных данных.
Легким движением руки это:
Превращается в это:
Вывод тот же, что и у Get-AdvancedOptions, но работает в разы быстрее.
Теперь к Get-VM. Он выполняется не быстро, так как имеет дело со сложными объектами. Встает логичный вопрос: а зачем нам в данном случае лишняя информация и монструозный PSObject, когда нам всего-то нужно имя ВМ, ее состояние и значение хитрого атрибута?
К тому же, из скрипта ушел тормоз в лице Get-AdvancedOptions. Применение Runspace Pools теперь выглядит излишеством, так как больше нет необходимости в распараллеливании медленной задачи в потоках с приседаниями при передаче сессии. Инструмент хороший, но не для этого кейса.
Смотрим на вывод ExtensionData: это не что иное, как объект Get-View.
Призовем древнюю технику мастеров PowerShell: one line с применением фильтров, сортировок и группировки. Весь предыдущий ужас элегантно схлопывается в одну строку и выполняется в одной сессии:
9 секунд для почти 10k объектов с фильтрацией по нужному условию. Отлично!
Вместо заключения
Приемлемый результат напрямую зависит от выбора инструмента. Зачастую сложно сказать наверняка, что именно следует выбрать для его достижения. Каждый из перечисленных методов ускорения скриптов хорош в границах своей применимости. Надеюсь, данная статья поможет вам в нелегком деле постижения основ автоматизации процессов и их оптимизации в вашей инфраструктуре.
Автор благодарит всех участников коммуны за помощь и поддержку при подготовке статьи. Даже тех, у кого лапки. И даже у кого лапок нет, как у удава.
Примеры пространств выполнения
В этом разделе содержится пример кода, демонстрирующий использование различных типов пространств выполнения для синхронного и асинхронного запуска команд. с помощью Microsoft Visual Studio можно создать консольное приложение, а затем скопировать код из разделов этого раздела в ведущее приложение.
в этом разделе
Примеры ведущих приложений, которые создают пользовательские интерфейсы узлов, см. в разделе примеры пользовательских узлов.
Пример Runspace01 В этом примере показано, как использовать класс System. Management. Automation. PowerShell для синхронного выполнения командлета Get-Process и вывода его выходных данных в окне консоли.
Пример Runspace03 В этом примере показано, как использовать класс System. Management. Automation. PowerShell для синхронного выполнения скрипта и для решения неустранимых ошибок. Скрипт получает список имен процессов, а затем извлекает эти процессы. Результаты выполнения скрипта, включая вызванные им устранимые ошибки, отображаются в окне консоли.
Пример Runspace04 В этом примере показано, как использовать класс System. Management. Automation. PowerShell для выполнения команд и перехвата ошибок, возникающих при выполнении команд. Выполняются две команды, и последняя получает недопустимый аргумент параметра. В результате объекты не возвращаются, и возникает ошибка завершения.
Пример Runspace08 В этом примере показано, как добавить команды и аргументы в конвейер объекта System. Management. Automation. PowerShell и как выполнять команды синхронно.
Пример Runspace09 В этом примере показано, как добавить скрипт в конвейер объекта System. Management. Automation. PowerShell и как запустить скрипт в асинхронном режиме. События используются для обработки выходных данных скрипта.
Пример Runspace11 В этом примере показано, как использовать класс System. Management. Automation. проксикомманд для создания команды прокси, которая вызывает существующий командлет, но ограничит набор доступных параметров. Прокси-команда затем добавляется в начальное состояние сеанса, который используется для создания ограниченного пространства выполнения. Это означает, что пользователь может получить доступ к функциям командлета только с помощью прокси-команды.
PowerShell, дамп моего опыта
Введение
Эта статья адресована тем, кто уже познакомился с основами PowerShell, запускал какие-то скрипты со stackexchange и, вероятно, имеет свой текстовый файл с теми или иными сниппетами облегчающими повседневную работу. Целью её написания есть уменьшение энтропии, увеличение читаемости и поддерживаемости кода PowerShell используемого в вашей компании и, как следствие, повышение продуктивности администратора работающего с ним.
На своём предыдущем месте работы я в силу специфики задач и несовершенства мира, сильно прокачал навык работы с PowerShell. После смены работы нагрузки такого рода сильно снизились и всё что было вот-вот еще на кончиках пальцев стало всё глубже тонуть под опытом решения новых задач на новых технологиях. От того эта статья претендует быть лишь тем, чем себя объявляет, раскрывая список тем, который на мой взгляд был бы полезен мне самому лет 7 назад, тогда, когда моё знакомство с этим инструментом только начиналось.
Если вы не понимаете почему PowerShell — объектно-ориентированный шелл, какие от этого появляются бонусы и зачем это вообще надо, я, не смотря на возгласы хейтеров посоветую вам хорошую книгу быстро вводящую в суть этой среды — Попов Андрей Владимирович, Введение в Windows PowerShell. Да, она про старую версию PS, да, язык обрел некоторые расширения и улучшения, но эта книга хороша тем, что описывая ранний этап развития этой среды невольно делает акцент лишь фундаментальных вещах. Синтаксический сахар, которым обросла среда я думаю вы быстро и без того воспримите поняв как работает сама концепция. Прочтение этой книги займет у вас буквально пару вечеров, возвращайтесь после прочтения.
Книга также доступна на сайте автора, правда я не уверен в том насколько лицензионно чисто такое использование: https://andpop.ru/courses/winscript/books/posh_popov.pdf
Стайл гайды
Оформление скриптов согласно стайлгайдам хорошая практика во всех случаях её применения, вряд ли тут может быть два мнения. Некоторые экосистемы позаботились об этом на уровне родного тулинга, из очевидного в голову приходит pep8 в сообществе Python и go fmt в Golang. Это бесценные инструменты экономящие время, к сожалению отсутствующие в стандартной поставке PowerShell, а от того переносящие проблему на нашу с вами голову. Единственным на текущий момент способом решения проблемы единого форматирования кода является вырабатывание рефлексов путем многократного написания кода удовлетворяющего стайлгайдам (на самом деле нет).
Стайлгайды в силу отсутствия официально утвержденных и подробно описанных компанией Микрософт были рождены в сообществе во времена PowerShell v3 и с тех пор развиваются в открытом виде на гитхабе: PowerShellPracticeAndStyle. Это заслуживающий внимания репозиторий для любого, кто хоть раз пользовался кнопкой «Save» в PowerShell ise.
Если попытаться сделать выжимку, то вероятно сведется она к следующим пунктам:
Comment Based Help
Мало того, при вызове скрипта будут работать подсказки по параметрам, будь то консоль PowerShell, будь то редактор кода:
Strict mode
PowerShell, как и многие другие скриптовые языки обладает динамической типизацией. У такого вида типизации есть много сторонников: написать простую, но мощную высокоуровневую логику — дело пары минут, но когда ваше решение начнет подбираться к тысяче строк, вы обязательно столкнетесь с хрупкостью такого подхода.
Автовывод типов неизменно на этапе тестирования формировавший массив в том месте где вы всегда получали набор элементов, обязательно подложит свинью в случае, если получит один элемент и вот уже в следующем условии, вместо проверки количества элементов вы получите количество символов или иной атрибут, в зависимости от типа элемента. Логика скрипта сломается, при этом среда исполнения сделает вид что все хорошо.
Установка строгого режима помогает избежать части такого рода проблем, но и требует от вас чуть больше кода, вроде инициализации переменных и явного приведения.
Для избежания такой проблемы, следует результат выполнения командлета явно привести к массиву:
Возьмите за правило всегда устанавливать строгий режим, это позволит вам избежать неожиданных результатов выполнения ваших скриптов.
Обработка ошибок
ErrorActionPreference
Просматривая чужие скрипты, например на гитхабе, я часто вижу либо полное игнорирование механизма обработки ошибок, либо явное включение режима тихого продолжения работы в случае возникновения ошибки. Вопрос обработки ошибок, безусловно, не самый простой в программировании вообще и в скриптах в частности, но игнорирования он определенно не заслуживает. По-умолчанию, PowerShell в случае возникновения ошибки выводит её и продолжает работу (я немного упростил концепцию, но ниже будет ссылка на гит-книгу по этой теме). Это удобно, в случае если вам срочно нужно распространить обновление широкоиспользуемой в домене программы на все машины, не дожидаясь пока она разольется на все точки деплоя sccm или распространится иным используемым у вас способом. Неприятно прерывать и перезапускать процесс в случае если одна из машин выключена, это правда.
С другой стороны, если вы делаете сложное резервное копирование системы состоящей из более чем одного файла данных более чем одной части информационной системы, вам нужно быть уверенным что ваша резервная копия консистентна и что все необходимые наборы данных были скопированы без ошибок.
try/catch
В обработчике ошибок можно делать матчинг по типу исключения и оперировать потоком исполнения или, например, добавлять чуть больше информации. Не смотря на то, что используя операторы try/catch/throw/trap можно выстроить весь поток выполнения скрипта, следует категорически этого избегать, так как такой способ оперирования выполнением мало того, что считается крайним антипаттерном, из разряда «goto-лапши», так еще и сильно просаживает производительность.
Стоит отметить оператор trap — это глобальная ловушка ошибок. Она ловит все что не было обработано на более низких уровнях, либо выброшено наружу из обработчика исключения в силу невозможности самостоятельного исправления ситуации.
Помимо описанного выше объектно-ориентированного подхода исключений, PowerShell предоставляет и более привычные, совместимые с другими «классическими» шеллами концепции, например потоков ошибок, кодов возврата и переменных накапливающих ошибки. Всё это безусловно удобно, иногда безальтернативно, но выходит за рамки этого, в целом обзорного, топика. К счастью на эту тему есть хорошая открытая книга на github.
Код логгера, который я использую когда нет уверенности что в системе будет PowerShell 5 (где можно описать более удобно класс логгера), попробуйте его, он может быть вам полезен в силу своей простоты и краткости, дополнительные методы вы, уверен, добавите без труда.:
Повторю идею — не игнорируйте обработку ошибок. Это сэкономит ваше время и нервы в длительной перспективе.
Не думайте, что выполнение скрипта несмотря ни на что — хорошо. Хорошо — это вовремя упасть не наломав дров.
Инструменты
Начать улучшение инструментов работы с PowerShell стоит безусловно с эмулятора консоли. Я часто слышал от сторонников альтернативных ос, что консоль в windows плоха и что это вообще не консоль, а дос и проч. Мало кто адекватно мог сформулировать свои претензии на этот счет, но если кому-то удавалось, то на деле оказывалось что все проблемы можно решить. Подробнее о терминалах и новой консоли в windows на хабре уже было, там всё более чем ок.
Первым делом стоит установить Conemu или его сборку Cmder, что не особо важно, так как на мой взгляд по настройкам в любом случае стоит пробежаться. Я обычно выбираю cmder в минимальной конфигурации, без гита и прочих бинарей, которые ставлю сам, хотя несколько лет тюнил свой конфиг для чистой Conemu. Это действительно лучший эмулятор терминала для windows, позволяющий разделять экран (для любителей tmux/screen), создавать вкладки и включить quake-style режим консоли:
Conemu
Следущим шагом рекомендую поставить модули: oh-my-posh, posh-git и PSReadLine. Первые два сделают промт приятнее, добавив в него информацию о текущей сессии, статусе последней выполненной команды, индикатор привелегий и статус гит-репозитория в текущем расположении. PSReadLine сильно прокачивает промт, добавляя например поиск по истории введенных команд (CRTL + R) и удобные подсказки для командлетов по CRTL + Space:
Visual Studio Code
Редактор. Всё самое плохое, что я могу сказать про PowerShell, относится сугубо к PowerShell ISE, те кто видели первую версию с тремя панелями врядли забудут этот опыт. Отличающаяся кодировка терминала, отсутствие базовых возможностей редактора, вроде автоматического отступа, автозакрывающихся скобок, форматирования кода и целый набор порождаемых им антипаттернов про которые я вам не расскажу (на всякий случай) — это все про ISE.
Не используйте его, используйте Visual Studio Code с расширением PowerShell — тут есть всё, чего бы вы не захотели (в разумных пределах, конечно). И не забывайте, что в PoweShell до шестой версии (PowerShell Core 6.0) кодировка для скриптов — UTF8 BOM, иначе русский язык сломается.
Помимо подсветки синтаксиса, подсказки методов и возможности дебага скриптов, плагин устанавливает линтер, который так же поможет вам следовать закрепившимся в сообществе практикам, например в один клик (по лампочке) развернет сокращения. На деле это обычный модуль, который можно поставить и независимо, например добавить его в ваш пайплайн подписи скриптов: PSScriptAnalyzer
Для того чтобы получить новую консоль conpty следует установить флаг в настройках, позже, вероятно, этот совет будет неактуальным.
Стоит запомнить, что любое действие в VS Code можно выполнить из центра управления, вызываемого комбинацией CTRL + Shift + P. Отформатировать вставленный из чата кусок кода, отсортировать строки по алфавиту, поменять индент с пробелов на табы и проч, всё это в центре управления.
Например включить полный экран и расположение редактора по центру:
Source Control; Git, SVN
Часто у системных администраторов Windows есть фобия разрешения конфликтов в системах контроля версий, вероятно от того, что если представитель этого множества пользуется git, то зачастую один и не встречается ни с какими проблемами такого рода. С vscode разрешение конфликтов сводится буквально к кликам мыши на тех частях кода что нужно оставить или заместить.
Вот эти надписи между 303 и 304 строкой кликабельны, стоит нажать на все такие что появляются в документе в случае конфликта, сделать коммит фиксирующий изменения и отправить изменения на сервер. У — Удобство.
О том как работать с системами контроля версий доступно и с картинками написано в доке vscode, пробегитесь глазами до конца там кратко и хорошо.
Snippets
Сниппеты — своего рода макросы/шаблоны позволяющие ускорить написание кода. Однозначно must see.
Быстрое создание объекта:
Рыба для comment-based help:
В том случае, если командлету нужно передать большое количество параметров, есть смысл использовать сплаттинг.
Вот сниппет для него:
Просмотр всех доступных сниппетов доступен по Ctrl + Alt + J:
Если после этого у вас появилось желание продолжить улучшать свое окружение, но вы еще ниразу не слышали про осом-листы, то вот же, положил. Так же, если у вас есть свой набор расширений пригождающихся вам при написании PowerShell-скриптов, буду рад увидеть их список в коментариях.
Производительность
Тема производительности не такая простая как может показаться на первый взгляд. С одной стороны, преждевременные оптимизации могут сильно снизить читаемость и поддерживаемость кода, сэкономив 300мс времени выполнения скрипта, обычное время работы которого может быть десяток минут, применение их в таком случае безусловно деструктивно. С другой — есть несколько довольно простых приемов, повышающих как читаемость кода так и скорость его работы, использовать которые вполне уместно на постоянной основе. Ниже я расскажу о некоторых из них, в случае если же перформанс для вас всё, а читаемость уходит на второй план в силу жестких временных ограничений простоя сервиса на время обслуживания, рекомендую обратиться к профильной литературе.
Pipeline и foreach
Самый простой и всегда рабочий способ поднять производительность — уйти от использования пайпов. В силу типобезопасности и удобства работы ради, PowerShell пропуская элементы через пайп оборачивает каждый из них в объект. В dotnet языках такое поведение называется боксинг. Боксинг хорош, он гарантирует безопасность, но у него есть своя цена, которую порой нет смысла платить.
Первым шагом поднять производительность и на мой взгляд повысить читаемость можно убрав все применения командлета Foreach-Object и заменив его на оператор foreach. Вас может смутить то, что на самом деле это две разных сущности, ведь foreach является алиасом для Foreach-Object — на практике главное отличие в том, что foreach не принимает значения из пайплайна, при этом работает по опыту до трех раз быстрее.
Представим задачу: нам нужно обработать большой лог для формирования какой-то его производной, например, выбрать и привести к другому формату ряд записей в нём:
When reading from and writing to binary files, use the AsByteStream parameter and a value of 0 for the ReadCount parameter. A ReadCount value of 0 reads the entire file in a single read operation. The default ReadCount value, 1, reads one byte in each read operation and converts each byte into a separate object, which causes errors when you use the Set-Content cmdlet to write the bytes to a file unless you use AsByteStream
На моём файле лога размером чуть более одного гигабайта преимущество второго подхода почти в три раза:
Следующая задача — как-то обработать этот лог, напишем решение «в лоб» с добавлением в каждую отобранную строчку текущей даты и запись в файл через пайп:
Результаты выполнения замеренные командлетом Measure-Command :
Попробуем улучшить результат. Думаю многим очевидно, что запись каждой отдельной строки в файл не самая оптимальная операция, куда лучше сделать накопительный буфер который периодически сбрасывать на диск, в идеале сбросить его один раз. Так же стоит отметить, что строки в PowerShell неизменяемы и любая операция со строкой порождает новую область в памяти, куда записывается новая строка, а старая остается ждать сборщик мусора — это дорого и по скорости и по памяти. Для решения этой проблемы в дотнете есть специализированный класс, который позволяет изменять строки, при этом инкапсулируя логику более аккуратного выделения памяти и имя ему — StringBuilder. При создании класса выделяется буфер в оперативной памяти в который добавляются новые строки без повторного выделения памяти, в том случае если размера буфера не хватает для добавления новой строки, то создается новый вдвое большего размера и работа продолжается с ним. Помимо того что такая стратегия сильно уменьшает количество выделений памяти, её еще можно подтюнить если знать примерный объем памяти который будут занимать строки и задать его в конструкторе при создании объекта.
Время выполнения этого кода всего 5 минут, вместо прошлых двух с половиной часов:
В обоих случаях PowerShell ограничивал себя тремя гигабайтами памяти:
Во время работы StringBuilder на каждой итерации выводит в консоль информацию о выделенной на текущий момент памяти и размере добавленных элементов из которой можно взять примерные значения для установки в конструкторе:
Всем хорош предыдущий подход, кроме разве что того, что 3Гб это 3Гб. Попробуем уменьшить потребление памяти и заиспользуем другой dotnet-класс написаный для решения таких проблем — StreamReader.
Время выполнения осталось практически тем же, но потребление памяти и его характер изменились. Если в предыдущем примере при чтении файла в памяти занималось место сразу под весь файл, у меня это больше гигабайта, а работа скрипта характеризовалась утилизацией трех гигабайт, то при использовании стримридера, занятая процессором память медленно увеличивалась пока не дошла до 2Гб. Конечный объем занятой памяти я заскринить не успел, но есть скрин того что происходило ближе к концу работы:
Поведение программы по расходу памяти вполне очевидно — вход у неё грубо говоря «труба», а выход — наш StringBuilder — «бассейн» который и разливается до конца работы программы. Зададим размер буфера, что бы убрать лишние аллокации (я выбрал 100МБ) и начнем сбрасывать содержимое в файл при приближении к концу буфера. Последнюю проверку я реализовал в лоб — сравниваю прошел ли буфер отметку в 90% от общего размера (может быть эту операцию имеет смысл вынести из цикла, проверьте сами разницу во времени):
Максимальное потребление памяти составило 1Гб при почти той же скорости выполнения:
Безусловно результаты по абсолютным числам утилизированной памяти будут отличаться от одной машины к другой, всё зависит от того сколько памяти вообще доступно на машине и соответственно насколько агрессивным будет её освобождение. Если память для вас критична, а несколько процентов производительности не так, то можно еще уменьшить её потребление заиспользовав StreamWriter, он как стримридер, только стримрайтер 😉 Оставлю вам его для самостоятельного изучения, а то мне уже кажется я тут засиделся, ибо конца этому нет.
Нативные бинарники
Часто, проникшись всем удобством и мощностью PowerShell администраторы начинают стремиться использовать встроенные командлеты вместо системных бинарников, с одной стороны их сложно в этом упрекнуть — удобство, с другой: PowerShell — в первую очередь шелл и запуск бинарников его прямая задача, с которой он отлично справляется.
Пример задачи, в прошлом решенной мной с помощью StringBuilder и вызова консольной команды dir — получение относительных путей всех файлов в каталоге и подкаталогах (большого количества файлов). С использованием нативной команды время выполнения меньше в пять раз:
Код этот я писал несколько лет назад и некоторые решения в нем могут показаться не лучшими, но проводить глубокую ревизию его мне в данный момент времени крайне лениво (иначе конца написанию этой статьи не будет), если у вас есть желание сделать его лучше — добро пожаловать в коментарии.
Использование описанного выше враппера до банального простое, стоит лишь добавить обработку исключений:
Послевкусие
Производительность скриптов тема обширная и отчасти холиварная — микрооптимизации могут забрать времени на реализацию и тестирование больше чем принесут, поддерживаемость и читаемость кода может снизиться настолько, что цена его поддержки будет выше чем профит от использования такого решения; вместе с тем, есть ряд простых рекомендаций делающих ваш код проще, понятне и быстрее, стоит лишь начать их использовать:
использовать оператор foreach вместо командлета Foreach-Object в скриптах;
минимизировать количество пайплайнов;
читать/писать файлы разом, а не построчно;
использовать StringBuilder и прочие специализированные классы;
профилировать код и понимать узкие места, прежде чем что-то оптимизировать;
не стыдиться вызывать нативные бинарники (пастить «батники» в скрипты не стоит);
И пожалуй главное еще раз: не спешите оптимизировать что-то без реальной необходимости, преждевременная оптимизация может всё испортить.
Бывает так, что вы уже оптимизировали всё, что казалось необходимым и пришли к некоторому компромису, между читаемость и скоростью, но то ли данных стало больше, то ли дальнейшие алгоритмические трюки себя не оправдывают, а сократить время работы нужно. В этом случае незаменимым помощником может стать параллельность исполнения некоторых частей кода. Убедиться стоит разве что в том, что не будет проблем с IO, если вы вдруг решили что скорости диска хватит на любое количество потоков вмещающихся в память.
Вот так происходит первая загрузка свежеустановленной Windows Server 2019 в Hyper-V на ssd (решилось миграцией виртуалки на hdd):
Со второй версии PowerShell доступны командлеты для работы с заданиями ( Get-Command *-Job ), подробнее можно почитать например тут.
Ничего концептуально сложного в них нет, оформляем скриптблок, запускаем задание, получаем результаты в нужный момент:
Пример выше призван уместить необходимые командлеты на минимальной единице площади, а не является хорошим паттерном написания асинхронного кода — не стоит делать асинхронный вызов для того, чтобы подождать его завершения. В качестве нестареющего примера использования джобов я рекомендую вам разобраться и подебажить вот этот скрипт распределенного пинга подсети.
Если вы решили не открывать ссылку, еще одна попытка с моей стороны:
https://xaegr.wordpress.com/2011/07/12/threadping/
Проблема, которая вроде бы и не проблема, но обозначена — каждый джоб хочет немного памяти, что бы быть быстрее и запускается полноценным процессом операционной системы со всеми плюсами и минусами этого подхода. Вот так, например, умирает приведенный выше джоб (50 мегабайт — это 50 мегабайт):
Джобы помогут решить любые ваши задачи параллельно и сделать это удобно. Обязательно изучите этот механизм, джобы — это лучший выбор для простого решения задачи на довольно высоком уровне абстракции, при этом уложиться получится в минимальное количество строк. Помните одну важную вещь — ваши скрипты должны быть читаемыми для людей которые придут после вас, скрипты пишут для людей.
Но бывает так, что этой абстракции перестает хватать в силу архитектурных ограничений такого решения, например, сложно в такой парадигме сделать интерактивный гуи с биндингом значений на форме к каким-то переменным.
Runspaces
Концепции ранспейсов посвещена целая серия статей статей в блоге майкрософта и я очень рекомендую обратиться к первоисточнику — Beginning Use of PowerShell Runspaces: Part 1. Коротко, ранспейс — это отдельный поток PowerShell который работает в том же процессе операционной системы, от того не имея оверхеда на новый процесс. Если концепция легких потоков вам нравится и вы хотите пускать их десятками (нет, концепции каналов в PowerShell нет), то у меня для вас хорошая новость: для удобства вся низкоуровневая логика вот в этом репозитарии модуля на гитхаб (там есть гифки) уже обернута в более знакомую концепцию джобов. А пока покажу как работать с ними руками, но первую ссылку из этого абзаца не забывайте посетить в любом случае.
В качестве примера использования ранспейсов могу привести скелет простой WPF формы, отрисовка которой происходит в том же потоке операционной системы что и основной процесс PowerShell, но в отдельном потоке рантайма. Взамодействие с ним происходит через потокобезопасный хэштейбл — вам не нужно писать никаких мьютексов, всё уже работает. Плюс такого подхода — вы можете в основном скрипте реализовать любой сложности и длительности работы алгоритм, блокировка которым основного потока исполнения не приведет к «зависанию» формы. Пруф в последней строке скрипта.
В конкретном примере запускается только один ранспейс, хотя ничего не мешает вам породить еще парочку в случае необходимости и завести им пул для удобства.
Еще один пример работы с WPF можете посмотреть в моём репозитории на github, там один поток и всё довольно просто, ну и еще он позволяет читать smart диска: https://github.com/snd3r/GetDiskSmart/. А ещё там можно посмотреть пример биндинга объектов к форме, когда работает магия MVVM:
Если на вашем компьютере не стоит старшая Visual Studio, например потому что ваша организация не удовлетворяет требований к бесплатному использованию Community Edition или у вас нет желания добавлять в систему программу установка которой будет необратима без резервной копии раздела, то на гитхабе есть простой инструмент для рисования простых xaml-форм для wpf — https://github.com/punker76/kaxaml
Вместо заключения
PowerShell — мощная и удобная среда для работы с Windows-инфраструктурой. Он хорош концептуально, он удобен своим синтаксисом и самодокументирующими названиями командлетов, он может в перформанс себя как среды и вас как специалиста, стоит лишь разобраться в концепциях которыми он оперирует и начать получать удовольствие.
Когда я начинал писать эту статью, в планах была небольшая заметка по оформлению кода и наглядной демонстрации преимуществ ответственного отношения к стайлгайдам, под названием «PowerShell, хорошие практики», но Остапа понесло. Опыта написания такого размера технических статей у меня до этого небыло, поэтому прошу извинить за некоторую сумбурность повествования — писалось всё последовательно из головы, в лучших традициях снятия дампа сознания. При этом я старался давать ссылки на заслуживаюшие внимания ресурсы и рекомендую вам сделать все их фиолетовыми. Эти ссылки хоть на деле и из первой страницы гугла, но во-первых гугол у всех разный, во-вторых я все же сверял свои ожидания с контентом и только в случае совпадения добавлял в статью.
Если у вас есть что добавить из своего опыта — добро пожаловать в комментарии.