Mind Your Powershell Efficiency Optimizations

A lazy Sunday morning post!

As most would agree, Powershell is the most powerful Windows administration tool ever seen. In my opinion, you cannot continue to be a Windows admin without learning it. However, Powershell is not breaking any speed records. In fact it can be downright slow. (After all, it's called Power-shell, not Speed-shell.)

So, as developers or sysadmins or devopsapotami or anyone else who writes Powershell, I implore you to not further sully Powershell's reputation for being slow by taking the time to benchmark and optimize your script/code.

Let's look at an example.

$Numbers = @()
Measure-Command { (0 .. 9999) | ForEach-Object { $Numbers += Get-Random } }

I'm simply creating an array (of indeterminate size) and proceeding to fill it with 10,000 random numbers.  Notice the use of Measure-Command { }, which is what you want to use for seeing exactly how long things take to execute in Powershell.  The above procedure took 21.3 seconds.

So let's swap in a strongly-typed array and do the exact same thing:

[Int[]]$Numbers = New-Object Int[] 10000
Measure-Command { (0 .. 9999) | ForEach-Object { $Numbers[$_] = Get-Random } }

We can produce the exact same result, that is, a 10,000-element array full of random integers, in 0.47 seconds.

That's an approximate 45x speed improvement.

We called the Get-Random Cmdlet 10,000 times in both examples, so that is probably not our bottleneck. Using [Int[]]$Numbers = @() doesn't help either, so I don't think it's the boxing and unboxing overhead that you'd see with an ArrayList. Instead, it seems most likely that the dramatic performance difference was in using an array of fixed size, which eliminates the need to resize the array 10,000 times.

Once you've got your script working, then you should think about optimizing it. Use Measure-Command to see how long specific pieces of your script take. Powershell, and all of .NET to a larger extent, gives you a ton of flexibility in how you write your code. There is almost never just one way to accomplish something. However, with that flexibility, comes the responsibility of finding the best possible way.

Comments (2) -

Jason Scott 9/26/2013 2:33:37 PM

Interesting, good to know.  You may [not] be surprised that a hash is nearly as fast as the fixed array, and sometimes faster if you wrap with 'foreach' instead of 'Foreach-Object'

$Numbers = @{}
Measure-Command { ( 0 .. 9999 ) | Foreach-Object { $Numbers[$_] = Get-Random } }
~0.80 sec

$Numbers = @{}
Measure-Command { foreach ( $i in ( 0 .. 9999 ) ) { $Numbers[$i] = Get-Random } }
~0.45 sec

Hey, thanks for stopping by and commenting.  I agree, hash tables are the bomb.  Incidentally, I find that to be the #1 reason why you want to use [PSObject]/[PSCustomObject] instead of just a plain ol' System.Object, because PSCustomObjects contain a special constructor that allows them to be built using a hash table, which makes them much faster, in general.  

Comments are closed