.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