Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.
Коллега, Вася Гусев в посте Непонятные штуки - $_ и % рассказал интересную и полезную тему про циклы. Однако я хочу немного дополнить этот рассказ. Я хочу показать некоторые технические нюансы использования цикла вида Foreach-Object и Foreach на практических примерах.
1) задача: произвести простое последовательное переприсвоение каждого элемента большого массива в другую переменную. Для решения этой задачи мы сгенерируем массив случайных чисел:
[vPodans] $a = 1..50000 | %{get-random}
И каждый элемент массива $a переприсвоим в переменную $X. А так же измерим время выполнения цикла. Для измерения времени работы цикла я буду использовать команду Measure-Command, которая показывает время исполнения команды с достаточно высокой точностью. Сперва переменную $a перенаправим по конвейеру в цикл Foreach-Object.
[vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7633872 [vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7561438 [vPodans] (Measure-Command {$a | Foreach-Object {$x = $_}}).TotalSeconds 4,7858102 [vPodans]
Я повторил команду 3 раза и мы получили среднее время обработки цикла равным примерно 4,7 секунд. Теперь повторим ту же операцию, но с использованием Foreach.
[vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,0465927 [vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,04518 [vPodans] (Measure-Command {Foreach ($b in $a) {$x = $b}}).TotalSeconds 0,0465084 [vPodans]
Здесь же мы видим, что цикл в среднем выполняет ту же операцию за 0,046 секунды! Или иными словами Foreach почти в 100(!) раз быстрее, чем Foreach-Object. При проведении простой обработке большого массива мы видим заметное преимущество Foreach. Однако, от типа данных и типа их обработки это число (100 раз) может изменяться как в сторону увеличения, так и уменьшения. Для этого обратимся к примеру 3 и 4.
2) Задача: выбрать все записи в журнале System (в моём случае в журнале находится 42855 записей) журнала событий и записать сообщения в текстовый файл. Данную задачу можно решить при помощи цикла Foreach-Object и Foreach. В отличии от предыдущего примера мы сталкиваемся с новым фактором влияния - запись на диск. И снова сперва будем использовать Foreach-Object.
Get-Process -Id $PID (Measure-Command {Get-EventLog System | %{$_.message >> events.txt}}).TotalSeconds Get-Process -Id $PID
И вот результаты выполнения данного скрипта:
[vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 219 7 37424 35568 178 0,98 4356 powershell [vPodans] (Measure-Command {Get-EventLog System | %{$_.message >> events.txt}}).TotalSeconds 249,4354958 [vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 229 7 42648 42116 186 229,48 4356 powershell [vPodans]
Здесь я добавил команду Get-Process, которая покажет нам некоторые моменты, которые в первых двух примерах были неактуальны. Итак, выгрузку тела сообщений журнала System в файл цикл Foreach-Object выполнил за 249 секунд, при этом показав незначительное увеличение потребляемой памяти. К слову говоря, размер файла составил 21,5МБ. Хочу отметить, что цифры потребления памяти очень сильно расходятся с показаниями Task Manager, который во время инициализации консоли показывает 17,5МБ потребления памяти и 22МБ после завершения работы.
Get-Process -Id $PID (Measure-Command {$events = Get-EventLog System}).TotalSeconds (Measure-Command {Foreach ($event in $events) {$event.message >>events.txt}}).TotalSeconds Get-Process -Id $PID
Скрипт выполняет точно такую же операцию, только я добавил ещё один временной штамп, когда переменная $events будет заполнена и будет подана в foreach на разбор. И вот, что я получил:
[vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 218 7 37400 35560 178 0,98 512 powershell [vPodans] (Measure-Command {$events = Get-EventLog System}).TotalSeconds 10,8001047 [vPodans] (Measure-Command {Foreach ($event in $events) {$event.message >>events.txt}}).TotalSeconds 340,2231286 [vPodans] Get-Process -Id $PID Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 237 7 116652 114432 270 331,39 512 powershell [vPodans]
Итак, что мы здесь видим? Мы видим, что время считывания журнала System в переменную $Events составило чуть больше 10 секунд. А вот уже обработка тела эвентов (запись каждого тела в файл) заняла 340 секунд! Если сравнивать с примером использования Foreach-Object мы не то, чтобы получили уменьшение времени выполнения, но даже увеличение на 91 секунду или 1,5 минуты и это без учёта 10 секунд чтения журнала в переменную. При этом мы так же наблюдаем внушительный прирост потребления памяти почти в 4 раза! По сведениям Task Manager потребление памяти увеличилось с 17,5МБ до 95МБ (даже в 5 раз).
На показательных примерах я продемонстрировал особенности работы цикла Foreach-Object и Foreach при обработке различных типов данных и различных операциях с ними - только простое последовательное переприсвоение и запись достаточно большого объёма данных в файл.
Выводы:
Изучив особенности работы каждого цикла мы порою можем значительно оптимизировать работу скрипта с использованием циклов и выбирать тот или иной метод по таким критериям, как скорость работы и объём потребляемой памяти при работе скрипта, который выполняет реальные задачи на реальном сервере.
Удачи! © One
Comments: