Home About

Published

- 3 min read

Supercharge PowerShell using ForEach and ArrayList

powershell
img of Supercharge PowerShell using ForEach and ArrayList

I feel like I don’t post enough about PowerShell related topics, so here is a useful and interesting one.

We all had to deal with a large amount of data processing at one point in our scripts. In this post, I will talk about how using ForEach loop and ArrayList can make a big impact on PowerShell performance.

I’ll first start with the slowest method as baseline and as I optimise the code, you can see the performance difference.

Scenario: I need to create 20,000 unique usernames. The usernames will be named from “user1” through to “user20000”.

The script will first generate a list of numbers ranging from 1 to 20,000, then I will loop through each number to create a unique username.

Baseline

The following script will get me what I wanted, however this is the slowest method without any optimisation.

   Measure-Command {
  $myList = @()
  1..20000| ForEach-Object {
    $myList += "user$($_)"
  }
} | Select-Object TotalMilliseconds | Format-List | Out-Host

Output: TotalMilliseconds : 8082.1093

Optimise 1 - use ForEach loop

To make the baseline script faster, we can use “ForEach” instead of “ForEach-Object”.

I_mportant note_: ForEach will allocates everything to memory before processing. For very large datasets, you may run out of memory.

   Measure-Command {
  $myList = @()
  ForEach ($num in (1..2000)) {
    $myList += "user$($num)"
  }
} | Select-Object TotalMilliseconds | Format-List | Out-Host

Output: TotalMilliseconds : 55.7869

That’s super fast! but we can try go even faster!

Optimise 2 - use ArrayList

By default, when we set a variable using @() in PowerShell, this will create us an Array object. Example:

   PS C:\> $myVar = @()
PS C:\> $myVar.GetType()

IsPublic IsSerial Name                                     BaseType
-------- -------- ---- --------
True     True     Object[]                                 System.Array

Since arrays are fixed length, each time we perform a ”+=” action, a new array needs to be created with that new additional element.

ArrayList does not have a fixed length so new elements can be added into the list with minimal overhead.

Here is my most optimal script.

   Measure-Command {
  [System.Collections.ArrayList]$myArrayList= @()
  ForEach ($num in (1..2000)) {
    $myArrayList.Add("user$($num)")
  }
} | Select-Object TotalMilliseconds | Format-List | Out-Host

Output: TotalMilliseconds : 5.4332

Can you believe that!? it only took 5.43 milliseconds to complete!?

Conclusion

With such optimisation in the code, I have successfully made this script 148,754% faster! dropping the original script down from 8082.1 milliseconds to only just 5.4 milliseconds.

It is important to remember that when using ForEach, you must be mindful of the memory consumption especially when the objects you are storing are very large.

I hope you find this tip useful and can start optimising your PowerShell scripts!