Wednesday, November 4, 2015

Improve PowerShell 5 script performance with class

Sometimes PowerShell scripts run a little slow. In many cases, you can speed them up by adding a little class.

PowerShell 5 introduced native class definition. At first glance, this is rather advanced, and not something most scripters will need to implement. But I discovered something that may make it useful even when you don’t need it. It’s fast.

PowerShell is kind of slow. That was part of the trade off at the core of its design. Because it not pre-compiled, it  is slow, but we can use it at the command line, and we can mix and match command line and scripts and pieces of scripts.

The difference in the performance of various pieces of code can have less to do with the speed of what you think it's doing, and more do to with the amount and efficiency of the overhead PowerShell needs to do it. For example, PowerShell gives us a lot of flexibility with how we work with variables, which comes at a cost of lots of overhead at run time for it to be able to handle any variable weirdness we throw at it.

When a PowerShell script is compiled at run time, code embedded in a custom class definition is compiled more efficiently, and requires less overhead to run. And then when you call that code, it can run much faster.

How much faster varies greatly from one chunk of code to the next.

For example, when I tested it using Measure-Command on an old virtual machine, this code ran in 20.6 seconds.

001
002
003
004
005
006
$x = 0
ForEach ( $i in 1..10000000 )
    {
    $x = $x + 1
    }
$x

The same code defined and called as a method of a custom class, took only 1.7 seconds to run.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
class MyMath
{
static [int] CountRealHigh()
    {
    $x = 0
    ForEach ( $i in 1..10000000 )
        {
        $x = $x + 1
        }
    return $x
    }
}

[MyMath]::CountRealHigh()

Other CPU intensive code I tested also saw improvements, though not all of it was as impressive as the first test.

This code ran in 13.0 seconds.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
function IsSemiPrime ( [int]$X )
    {
    If ( $X -le 1 ) { return $False }
    $Divisibles = 0
    ForEach ( $Divisor in 2..[math]::Sqrt( $X ) )
        {
        If ( $X % $Divisor -eq 0 )
            {
            $Divisibles++
            If ( $Divisibles -gt 1 )
                {
                return $False
                }
            }
        }
    return ( $Divisibles -eq 1 )
    }

1..100000 | Where { IsSemiPrime $_ } | Select -Last 1

While the same code in a custom class only took 10.6 seconds to run.

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
Class MyMath
{
static [int] IsSemiPrime ( [int]$X )
    {
    If ( $X -le 1 ) { return $False }
    $Divisibles = 0
    ForEach ( $Divisor in 2..[math]::Sqrt( $X ) )
        {
        If ( $X % $Divisor -eq 0 )
            {
            $Divisibles++
            If ( $Divisibles -gt 1 )
                {
                return $False
                }
            }
        }
    return ( $Divisibles -eq 1 )
    }
}

1..100000 | Where { [MyMath]::IsSemiPrime( $_ ) } | Select -Last 1

Of course, most of our scripts aren’t just doing math. So our bottlenecks are usually other things, like waiting for connections, or for restarts, or for our applications to respond, or whatever. So most of our scripts won’t benefit from this. And even when they do, most of the time we don’t care if our scripts take a few second longer to run. A slightly slower script is usually worth the trade off in time saved not having to write, test, and maintain faster but more complex code.

But sometimes there is a pressing need for a speedier script. When the need arises, try wrapping your script or functions in a class definition.

Thanks to Bruce Payette at Microsoft for some of the behind the scenes details on how this works. Bruce is working with Richard Siddaway on the PowerShell 5 version of his book, PowerShell in Action. If you buy it in advance, you get access to chapters as soon as they are written. It's a bit expensive, but if you sign up for email, they send out discount codes every Friday. I highly recommend it.

No comments:

Post a Comment