PowerShell: конвейер (pipeline) и оптимизация фильтров (filter)
При разработке обёрток для Яндекс.API и Outlook возник вопрос по оптимизации фильтрующих функций powershell. Подробности далее.
Для начала приведу пример кода:
filter a () { begin { Write-Verbose 'a - begin'; $test = 999; } process { Write-Verbose "a - process: $_, test=$test"; } end { Write-Verbose 'a - end'; } } filter b () { begin { Write-Verbose 'b - begin'; } process { Write-Verbose "b - process: $_"; $_ | a; } end { Write-Verbose 'b - end'; } } 0..3 | b;
Результат при этом получил следующий:
VERBOSE: b - begin VERBOSE: b - process: 0 VERBOSE: a - begin VERBOSE: a - process: 0, test=999 VERBOSE: a - end VERBOSE: b - process: 1 VERBOSE: a - begin VERBOSE: a - process: 1, test=999 VERBOSE: a - end VERBOSE: b - process: 2 VERBOSE: a - begin VERBOSE: a - process: 2, test=999 VERBOSE: a - end VERBOSE: b - process: 3 VERBOSE: a - begin VERBOSE: a - process: 3, test=999 VERBOSE: a - end VERBOSE: b - end
Как видно, толку от того, что a реализована как фильтрующая функция с выделением блоков begin
и end
, нет никакого – указанные блоки вызываются для каждого объекта в конвейере. Подобное поведения при выполнении дорогостоящих по времени операций (скажем – вызов внешнего REST или SOAP API) приведёт к резкой деградации производительности сценария. Наша цель – получить следующую картину:
filter a () { begin { Write-Verbose 'a - begin'; } process { Write-Verbose "a - process: $_"; } end { Write-Verbose 'a - end'; } } filter b () { begin { Write-Verbose 'b - begin'; } process { Write-Verbose "b - process: $_"; } end { Write-Verbose 'b - end'; } } 0..3 | b | a; VERBOSE: b - begin VERBOSE: b - process: 0 VERBOSE: a - begin VERBOSE: a - process: 0 VERBOSE: b - process: 1 VERBOSE: a - process: 1 VERBOSE: b - process: 2 VERBOSE: a - process: 2 VERBOSE: b - process: 3 VERBOSE: a - process: 3 VERBOSE: b - end VERBOSE: a - end
Разница очевидна? Блоки begin
и end
для функции a выполнены лишь по одному разу. Добьёмся того же эффекта при разработке обёртки для функции a
– функции c
:
filter a () { begin { Write-Verbose 'a - begin'; $test = 999; } process { Write-Verbose "a - process: $_, test=$test"; } end { Write-Verbose 'a - end'; } } filter c () { begin { Write-Verbose 'c - begin'; $A = ( { & (get-command a) @PSBoundParameters } ).GetSteppablePipeline(); $A.Begin( $true ); $test = 888; } process { Write-Verbose "c - process: $_, test=$test"; $A.Process( $_ ); } end { Write-Verbose 'c - end'; $A.End(); } } 0..3 | c;
Такая реализация обёртки даёт именно тот результат, что требуется:
VERBOSE: c - begin VERBOSE: a - begin VERBOSE: c - process: 0, test=888 VERBOSE: a - process: 0, test=999 VERBOSE: c - process: 1, test=888 VERBOSE: a - process: 1, test=999 VERBOSE: c - process: 2, test=888 VERBOSE: a - process: 2, test=999 VERBOSE: c - process: 3, test=888 VERBOSE: a - process: 3, test=999 VERBOSE: c - end VERBOSE: a - end
Да, прямо скажем – решение неочевидное, да и не особо красивое. Буду благодарен за более красивое решение.
Более красивым и читабельным выглядит следующий пример:
filter a () { begin { Write-Verbose 'a - begin'; $test = 999; } process { Write-Verbose "a - process: $_, test=$test"; } end { Write-Verbose 'a - end'; } } filter c () { begin { Write-Verbose 'c - begin'; $A = ( { A @PSBoundParameters ` | % { <# любой другой код и сколь угодно длинный конвейер #> } ` } ).GetSteppablePipeline( $myInvocation.CommandOrigin ); $A.Begin( $PSCmdlet ); $test = 888; } process { Write-Verbose "c - process: $_, test=$test"; $A.Process( $_ ); } end { Write-Verbose 'c - end'; $A.End(); } } 0..3 | c;
Такой код выглядит более читабельным, а если при этом требуется не одну функций в конвейер “вставить” вслед за собой, а целый конвейер – тогда такой вариант выглядит в разы “читабельнее”.
RSS комментарии
Обратная ссылка