Статья размещена автором Бетке Сергей Сергеевич

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;

Такой код выглядит более читабельным, а если при этом требуется не одну функций в конвейер “вставить” вслед за собой, а целый конвейер – тогда такой вариант выглядит в разы “читабельнее”.

Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка