Contents of this directory is archived and no longer updated.

Примечание: данный пост перепечатан в связи с закрытием бложиков на spaces.live.com, как имеющий какую-то ценность для автора и/или читателей.


Коллега, Вася Гусев в посте Непонятные штуки - $_ и % рассказал интересную и полезную тему про циклы. Однако я хочу немного дополнить этот рассказ. Я хочу показать некоторые технические нюансы использования цикла вида Foreach-Object и Foreach на практических примерах.

1) задача: произвести простое последовательное переприсвоение каждого элемента большого массива в другую переменную. Для решения этой задачи мы сгенерируем массив случайных чисел:

[vPodans] $a = 1..50000 | %{get-random}

И каждый элемент массива $a переприсвоим в переменную $X. А так же измерим время выполнения цикла. Для измерения времени работы цикла я буду использовать команду Measure-Command, которая показывает время исполнения команды с достаточно высокой точностью. Сперва переменную $a перенаправим по конвейеру в цикл Foreach-Object.

  • Пример 1
[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.

  • Пример 2
[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.

  • Пример 3. Чтение журнала System и выдача тела эвентов по конвейеру в цикл:
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МБ после завершения работы.

 

  • Пример 4. Чтение журнала System в перменную и обработка тела эвентов циклом Foreach:
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 при обработке различных типов данных и различных операциях с ними - только простое последовательное переприсвоение и запись достаточно большого объёма данных в файл.

Выводы:

  • Foreach-Object выгоден для сложной обработки большого объёма массивных данных и перенаправления их в небыстрые хранилища (например, в файл, где очень важным фактором становится невысокая скорость работы диска). Данный цикл работает достаточно быстро и характеризуется скромным потреблением памяти. Однако, когда мы обрабатываем данные несложными операциями (т.е. по сути важным фактором становится скорость работы центрального процессора и памяти), то Foreach-Object значительно уступает в скорости циклу Foreach и уступание может достигать колоссальных размеров (в примерах 1 и 2 он уступил в скорости в 100 раз).
  • Foreach выгоден при несложной обработке данных на лету без использования медленных посредников (как запись в файл), когда в основном используются только ресурсы процессора. За счёт увеличения скорости обработки данных в оперативной памяти увеличивается и потребление памяти. Но с увеличением сложности обработки (если взять пример 2 и сделать не переприсвоение, а простое математическое деление каждого элемента массива на число 3 и присвоение результата в переменную, то разница в скорости работы уже составит не 100 раз, а всего лишь 3 раза) скорость выполнения цикла заметно падает. Этот момент очень ярко выражается при использовании медленных посредников (жёсткий диск, сеть) может не только не давать прирост в скорости, но и даже снижать производительность по сравнению с Foreach-Object в схожих операциях. И в целом, Foreach характеризуется значительным увеличением потребления памяти. И чем сложнее обработка, тем более ощутимо увеличивается потребление памяти и снижается скорость работы, как это показано на примере 4.

Изучив особенности работы каждого цикла мы порою можем значительно оптимизировать работу скрипта с использованием циклов и выбирать тот или иной метод по таким критериям, как скорость работы и объём потребляемой памяти при работе скрипта, который выполняет реальные задачи на реальном сервере.

Удачи! © One


Share this article:

Comments:

Comments are closed.