PowerShell performance difference filter vs. function

Solution 1:

Unless the author gave more supporting evidence, maybe he was just full of hot air. You've run the test and got the result and proven him wrong.

Edit: From Jeffrey Snover's blog:

A filter is a function that just has a process scriptblock

That alone isn't enough to convince me that a filter is going to have a speed advantage over a function, given both have identical process blocks.

Also what sort of 1950's equipment is that guy on where it takes 4.6 seconds to add one to a number?

PS C:\Users\Ryan> Measure-Command { Filter AddOne { $_ + 1 }; AddOne 1 }

TotalMilliseconds : 7.7266


PS C:\Users\Ryan> Measure-Command { Function AddOne { $_ + 1 }; AddOne 1 }    

TotalMilliseconds : 0.4108

4.6 seconds is whack. Maybe the author was using some sort of CTP version of Powershell before the binaries were ngen'ed. :P

Finally, try your test in a new Powershell session, but in reverse order. Try the Function first and the Filter second, or vice versa:

PS C:\Users\Ryan> Measure-Command { Function AddOne { $_ + 1 }; AddOne 1 }    

TotalMilliseconds : 6.597    


PS C:\Users\Ryan> Measure-Command { Filter AddOne { $_ + 1 }; AddOne 1 }

TotalMilliseconds : 0.4055

See? The first one you run will always be slower. It was just about the .NET internals of having already loaded stuff into memory that makes the second operation faster, regardless of whether it's a function or a filter.

I will admit though that the Function still seems to be consistently faster than the Filter, regardless of how many times it's run.

Measure-Command { Function AddOne($Num) { Return $Num += 1 }; 1..50000 | AddOne $_ }

TotalMilliseconds : 13.9813

Measure-Command { Filter AddOne($Num) { Return $Num += 1 }; 1..50000 | AddOne $_ }

TotalMilliseconds : 69.5301

So the author was wrong... and now I don't feel bad for never having ever used a Filter instead of a Function before.

Solution 2:

Actually the difference is much smaller if you use the same $_ in both tests. I didn't investigate the cause, but I suppose it's because the author is not using the same approach in both tests. Also, console output can interfere in the results. If you cut these parts, the numbers are very similar. See:

Function AddOneFunction
{  
    process {
        $_ + 1
    }
}

Filter AddOneFilter
{ 
    $_ + 1
}

write-host "First"
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFilter } | select totalMilliseconds

write-host "Second"
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds
Measure-Command { 1..50000 | AddOneFunction } | select totalMilliseconds

The results will be very close, even if you change the order of the commands.

First

TotalMilliseconds
-----------------
        84.6742
        84.7646
        89.8603
        82.3399
        83.8195
Second
        86.8978
        87.4064
        89.304
        94.4334
        87.0135

The documentation also says that Filters are basically shortcuts to functions with only the process block. Functions, unless specified with a process block (or some other technique like using automatic variables such as $input), run once, don't use input and don't pass to the next command in the pipeline.

More info at https://technet.microsoft.com/en-us/library/hh847829.aspx and https://technet.microsoft.com/en-us/library/hh847781.aspx