.NET or not .NET - ForEach

Created March 22, 2023

.NET or not .NET this is the question, we will ask in this post

Lets find out if the .NET .ForEach() method is significantly faster than their equivalent in native PowerShell

The most of us are interested in making the code faster, with that in mind I want to have a look about foreach loops and and their .NET equivalent. I also want to see if the piped ForeachEach-Object{} may be is a game changer. Lets create some dummy-data first for looping, 3 simple files (100 lines, 10000 lines and 100000 lines) should be ok:

#create random files
$Sizes = 100,10000,100000
$Sizes.foreach({
    $Size = $_
    (0..$Size).ForEach({
        $((0..8).ForEach({
            [char] $(Get-Random -Minimum 65 -Maximum 90) 
        })) -join ""
    }) | Out-File -FilePath "C:\Temp\Perftest\$($Size)lines.txt" -Encoding utf8
})

Now we will create our performance table:

$TestList = New-Object -TypeName "System.Collections.Generic.List[pscustomobject]"
foreach($File in Get-Childitem -Path "C:\Temp\Perftest"){
    $FileContent = Get-Content -Path $File.FullName -Encoding utf8

    #native foreach loop
    $StopWatch = New-Object System.Diagnostics.Stopwatch
    $TempCollectionList = New-Object -TypeName "System.Collections.Generic.List[string]"
    $StopWatch.Start()
    foreach($line in $FileContent){
        $TempCollectionList.Add($Line)
    }
    $StopWatch.Stop()
    $TestList.add([PSCustomObject]@{
        Method = "Native foreach loop"
        Size = $File.BaseName
        TimeElapsed = $StopWatch.Elapsed
        TimeElapsedMS = $StopWatch.ElapsedMilliseconds
    })
    
    #.NET foreach Loop
    $StopWatch = New-Object System.Diagnostics.Stopwatch
    $TempCollectionList = New-Object -TypeName "System.Collections.Generic.List[string]"
    $StopWatch.Start()
    $FileContent.Foreach({
        $TempCollectionList.Add($Line)
    })
    $StopWatch.Stop()
    $TestList.add([PSCustomObject]@{
        Method = ".NET foreach loop"
        Size = $File.BaseName
        TimeElapsed = $StopWatch.Elapsed
        TimeElapsedMS = $StopWatch.ElapsedMilliseconds
    })
    
    #Piped foreach Loop
    $StopWatch = New-Object System.Diagnostics.Stopwatch
    $TempCollectionList = New-Object -TypeName "System.Collections.Generic.List[string]"
    $StopWatch.Start()
    $FileContent | ForEach-Object{
        $TempCollectionList.Add($_)
    }
    $StopWatch.Stop()
    $TestList.add([PSCustomObject]@{
        Method = "Piped foreach loop"
        Size = $File.BaseName
        TimeElapsed = $StopWatch.Elapsed
        TimeElapsedMS = $StopWatch.ElapsedMilliseconds
    })
}

Finally we can analyze our performance table:

Method Size TimeElapsed TimeElapsedMS
Native foreach loop 100000lines 00:00:00.1438553 143
.NET foreach loop 100000lines 00:00:00.2146542 214
Piped foreach loop 100000lines 00:00:00.4116282 411
Native foreach loop 10000lines 00:00:00.0200892 20
.NET foreach loop 10000lines 00:00:00.0388348 38
Piped foreach loop 10000lines 00:00:00.0354476 35
Native foreach loop 100lines 00:00:00.0000751 0
.NET foreach loop 100lines 00:00:00.0001173 0
Piped foreach loop 100lines 00:00:00.0002555 0

So we can say that in this test scenario the native foreach loop beats the .NET Method right away. Whats interesting here is, that the piped method is slightly faster in medium sized files in comparison to the .NET method. All tests have been made with PowerShell Version 7.x. If you like you can do this as well with an older version or with more complicated objects to iterate through. Let me know.

Thats all for now, best regards Christian