Performance (Part 3): Faster Pipeline Functions

by Oct 19, 2018

In previous tips we illustrated how you can improve loops and especially pipeline operations. To transfer this knowledge to functions, take a look at a simple pipeline-aware function which counts the number of piped elements:

function Count-Stuff
{
    param
    (
        # pipeline-aware input parameter
        [Parameter(ValueFromPipeline)]
        $InputElement
    )

    begin 
    {
        # initialize counter
        $c = 0
    }
    process
    {
        # increment counter for each incoming object
        $c++
    }
    end
    {
        # output sum
        $c
    }


}

When you run this function and count a fairly large number of objects, this is what you might get:

 
PS> $start = Get-Date
1..1000000 | Count-Stuff
(Get-Date) - $start

1000000

...
TotalMilliseconds : 3895,5848 
...
 

Now let’s turn the function into a “simple function” by avoiding attributes:

function Count-Stuff
{
    begin 
    {
        # initialize counter
        $c = 0
    }
    process
    {
        # increment counter for each incoming object
        $c++
    }
    end
    {
        # output sum
        $c
    }
}

Since you now have no way of defining pipeline-aware parameters, pipeline input surfaces as “$_” in the process block, and as “$input” as an enumerator to all received input in the end block. Note also that our counting example does not need any of these as it only counts the incoming input.

Here is the result:

 
$start = Get-Date
1..1000000 | Count-Stuff
(Get-Date) - $start

1000000

...
TotalMilliseconds : 690,1558 
...
 

Apparently, simple functions produce much less overhead with pipeline operations when a lot of objects are involved.

Of course, the effect becomes significant only when you pipe a high number of objects, but it increases when you do more complex things. For example, the code below produces 5-digit server lists, and using advanced functions, it took on our test system roughly 10 seconds:

function Get-Servername
{
    param
    (
        # pipeline-aware input parameter
        [Parameter(ValueFromPipeline)]
        $InputElement
    )

    process
    {
        "Server{0:n5}" -f $InputElement
    }
}



$start = Get-Date
$list = 1..1000000 | Get-ServerName
(Get-Date) - $start

The very same result was produced by a simple function in under 2 seconds (both in PowerShell 5.1 and 6.1):

function Get-ServerName
{
    process
    {
        "Server{0:n5}" -f $InputElement
    }
}



$start = Get-Date
$list = 1..1000000 | Get-ServerName
(Get-Date) - $start

Twitter This Tip! ReTweet this Tip!