Lightweight Performance Testing with PowerShell

PowerShell Team

When you write a script, a few possible concerns may be going through your mind:

 

·         Does my Script do what I want it to?

·         Can the script be read and understood by other people?

·         Is the script efficient?

 

The answers to one and two can be very subjective, so today I’ll help you figure out how to make your script efficient.  More precisely, I will help you to figure out the most efficient of a few different ways you can do something in Powershell.

 

Making efficient Powershell scripts can be tricky.  Many things that you can do in PowerShell with a Cmdlet, you can also do with a .NET or COM object or with a command line.  Also, some things within the Powershell language will have the same effect, but use different syntax and have slightly different performance.

 

Today, I’ll take a scenario that you can accomplish in multiple ways in PowerShell and I’ll show you how you can use Get-History or Measure-Command to do simple performance testing of what you just run, in order to help optimize your scripts.

 

The scenario is common enough: Within a foreach, you want to call a method on each member of the foreach, but you also want to put each item from the foreach into a collection.  So in order to avoid having your collection contain the items and the output from the method, you need to be able to throw away the result.

 

There are basically three ways to throw away the result of a method or command: by assigning the value to $null, by casting it to [void], or by using a pipeline to send the results to Out-Null.  Here are examples of each:

 

$mycollection | foreach { $null = $_.MyMethod() ; $_ } | <more cmdlet calls>

$mycollection | foreach { [void]$_.MyMethod() ; $_ } | <more cmdlet calls>

$mycollection | foreach { $_.MyMethod() | Out-Null ; $_ } | <more cmdlet calls>

 

I recently suggested that someone use Out-Null to do this, and a colleague challenged if this would be the fastest solution.  So I determined that I would figure out which solution was indeed the fastest.

 

It turns out language constraints are more efficient, and can be used safely provided you are using them to cast the result.  Casting the result will not change the underlying variable’s constraint.  This only happens when you have the constraint on the left side an assignment operator, so:

 

[void]$_.MyMethod() # will cast the return value of the method

$a = [void]$_.MyMethod() # will cast the return value of the method

[void]$a = $_.MyMethod() # Will constraint $a to Void

 

I personally find the third example selectively useful but potentially dangerous.  The reason comes from personal experience (I once have the third case in my script from copy/paste and did not realize it).  Having a throwaway variable is really useful (that’s why $null exists), but having one by accident is very dangerous.

 

The efficiency difference was pointed out to me, and so here is how I figured out the best (read, fastest) way to throwaway results:

 

In order to do this, I determined I would perform the same operation on a moderately sized input (1000 items).  My three tests were:

 

$result = (1..1000 | % { $_.GetType() | Out-Null; $_})

$result = (1..1000 | % { [void]$_.GetType(); $_})

$result = (1..1000 | % { $null = $_.GetType(); $_})

 

Then I ran the following item to collect the history of what I just did.

 

$results =  (Get-History)[-1..-3]

 

This collected the last three commands that I ran, by a nice little convention called negative indexing.  In Powershell, if you pass a negative number to a list, it returns that item in the list, starting at the end.

 

Now if you look at a history item, it contains the start execution time and the end execution time.  This means that I can look through my history and figure out which one was the fastest.  This will give me that information from any list of history items:

 

$results | % { $_.CommandLine ; ($_.EndExecutionTime -$_.StartExecutionTime).TotalMilliseconds }

 

$result = (1..1000 | % { $null = $_.GetType(); $_})

218.4

$result = (1..1000 | % { [void]$_.GetType(); $_})

234

$result = (1..1000 | % { $_.GetType() | Out-Null; $_})

670.8

 

Note: It is also possible to use Measure-Command to measure each of the expressions as well.  Instead of collecting the commands and their times from the history, you can always use Measure-Command –Expresson {ScriptBlock} to time how long a specific bit of Powershell code takes to run.

 

As you can see, Out-Null is actually the least efficient way you could choose to do this.  It appears the winner is assigning the output of an item to the variable $null, by a margin of just shy of 16 milliseconds (mileage may vary on your boxes).

 

So the fastest way is:

 

$mycollection | foreach { $null = $_.MyMethod() ; $_ } | <more cmdlet calls>

 

I hope this has been educational, and also helps provide you with a way to judge the faster of a few different possibilities of implementing the same script.

 

James Brundage[MSFT]

0 comments

Discussion is closed.

Feedback usabilla icon