tech.guitarrapc.cóm

Technical updates

PowerShell For / Foreach / Foreach-Object / ScriptBlock / Filter - Benchmark 2013

There is C# benchmark for For VS Foreach. It was very interesting to use in code.

C# - For Vs Foreach - Benchmark 2013

Introduction

I could not found greate article for "Which is better practive among "For" VS "Foreach" in PowerShell". I believe there should be some point of view about it, but let's try benchmark int with way how original C# site doing. What about PowerShelll 1.0 and 2.0? Sorry but please forgive me only about PowerShell 3.0.

Target

Like other program languages, PowerShell also have several Loop and I have decided to do benchmark, to compare the perfomance with for, foreach, Foreach-Object Cmdlet, ScriptBlock and filter.

for
foreach
Foreach-Object
ScriptBlock
Filter

As you know, they wrote like this in PowerShell.

for (<Initialization Pipeline>; <Evaluate Pipeline>;<increment Pipeline>){< Sentence >}
foreach (<Variable> in <Loop Pipeline>){< Sentence>}
<Parameter> | Foreach-Object {begin}{process}{end}
&{Sentence}
filter <Name>{param(<List of Parameter>) <Sentence>}

The Benchmark Result

Followed Original site.

Measured in miliseconds Collection length: 30000000
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 275899.00 265212.00 283013.40 274708.13
int Foreach 186429.45 176947.98 198700.93 187359.46
int Foreach-Object 1050280.06 1055265.18 1035663.52 1047069.58
int ScriptBlock 227393.92 240253.13 224370.40 230672.48
int Filter 237666.43 233423.09 233057.27 234715.60
int List<int> For 340137.03 355315.28 335932.48 343794.93
int Foreach 241257.27 260461.74 240227.87 247315.63
int Foreach-Object 1160124.71 1290480.25 1156974.90 1202526.62
int ScriptBlock 300991.85 322849.46 295052.61 306297.97
int Filter 342645.71 308032.91 348444.29 347944.69
int ArrayList<int> For 258247.16 280059.96 272251.21 270186.11
int Foreach 162259.60 174109.73 172552.03 169640.45
int Foreach-Object 997676.13 1079849.90 1057534.18 1045020.07
int ScriptBlock 209276.20 232152.56 214621.41 218683.39
int Filter 229675.65 230850.35 231555.98 231299.99  

Conclusion

Speed of Collections

  1. ArrayList (Fastest)
  2. int
  3. List (Slowest)

Data Type - Operation

All Datatype shows same disposition.

  • int (Array)
  • List
  • ArrayList

Foreach is the fastest, almost no difference between ScriptBlock and Filter. Foreach-Object seeems obviously slow.

  1. Foreach (Fastest)
  2. ScriptBlock
  3. Filter
  4. Foreach-Object (Slowest)

Which Loop type to be use

I should note such a cost for Foreach-Object Cmdlet.

Data Type Collection Type Loop Type Average v.s. int For v.s. int
int int For 274708.13 - -
int Foreach 187359.46 68.20% -
int Foreach-Object 1047069.58 381.16% -
int ScriptBlock 230672.48 83.97% -
int Filter 234715.60 85.44% -
int List<int> For 343794.93 125.15% 125.15%
int Foreach 247315.63 90.03% 132.00%
int Foreach-Object 1202526.62 437.75% 114.85%
int ScriptBlock 306297.97 111.50% 132.78%
int Filter 347944.69 126.66% 148.24%
int ArrayList<int> For 270186.11 98.35% 98.35%
int Foreach 169640.45 61.75% 90.54%
int Foreach-Object 1045020.07 380.41% 99.80%
int ScriptBlock 218683.39 79.61% 94.80%
int Filter 231299.99 84.20% 98.54%

It takes time V.S. "4 times of for", "5-6 time of foeach" and "4-4.5 time of ScriptBlock/Filter". Also ScriptBlock was next position of foreach and Filter shows almost same speed. I thought In-Action said Foreach-Object was just a one type of Filter, but benchmark result in it isn't. Filter should be takes cost for ScriptBlock {process{}} + set into PSObject:Function and was called. I don't to write foreach that foreach couldn't use in Pipeline, but now we can put Filter in alter choice. So I recomend as ...

  • foreach : for huge data like Log file and when it doesn't need to use the Pipeline.
  • Filter : It data was quite big but requried to use the Pipeline.
  • ScriptBlock : When using standalone, I mean not for the Pipeline or foreach statesment.
  • Foreach-Object : When required to pass Object through the Pipeline, also data was small and speed should be not considerble.

How about Collection size to take cost take exponential

There is no exponential trend but only propotion to Collection size. You can estimate time spend.

  1. Foreach (Fastest)
  2. ScriptBlock
  3. Filter
  4. Foreach-Object (Slowest)

Here's result with Collection size for 10,100,1000,10000,100000,1000000.

Measured in miliseconds Collection length: 10
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 0.15 0.22 0.21 0.19
int int Foreach 0.24 0.15 0.11 0.17
int int Foreach-Object 0.57 0.62 0.67 0.62
int int ScriptBlock 0.19 0.26 0.22 0.22
int int Filter 0.21 0.20 0.19 0.20
int List<int> For 0.19 0.24 0.16 0.20
int List<int> Foreach 0.13 0.33 0.13 0.19
int List<int> Foreach-Object 0.48 0.66 0.64 0.59
int List<int> ScriptBlock 0.30 0.28 0.24 0.27
int List<int> Filter 0.34 0.18 0.19 0.24
int ArrayList<int> For 0.12 0.23 0.13 0.16
int ArrayList<int> Foreach 0.09 0.19 0.10 0.13
int ArrayList<int> Foreach-Object 0.42 0.77 0.50 0.56
int ArrayList<int> ScriptBlock 0.15 0.25 0.20 0.20
int ArrayList<int> Filter 0.16 0.19 0.15 0.17

 

Measured in miliseconds Collection length: 100
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 1.08 0.90 1.08 1.02
int int Foreach 0.66 0.67 0.69 0.67
int int Foreach-Object 3.64 3.49 3.59 3.57
int int ScriptBlock 0.94 0.85 0.96 0.91
int int Filter 1.02 1.07 1.05 1.05
int List<int> For 1.24 1.15 1.17 1.19
int List<int> Foreach 0.92 2.91 0.97 1.60
int List<int> Foreach-Object 7.69 4.48 4.21 5.46
int List<int> ScriptBlock 1.16 1.20 1.07 1.14
int List<int> Filter 1.26 1.25 1.05 1.19
int ArrayList<int> For 1.01 0.99 1.13 1.04
int ArrayList<int> Foreach 0.67 0.77 0.57 0.67
int ArrayList<int> Foreach-Object 8.37 3.50 3.52 5.13
int ArrayList<int> ScriptBlock 0.76 0.79 0.75 0.77
int ArrayList<int> Filter 0.75 0.97 0.78 0.84

 

Measured in miliseconds Collection length: 1000
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 13.13 12.11 10.33 11.86
int int Foreach 7.38 6.54 6.70 6.88
int int Foreach-Object 41.38 39.61 37.85 39.61
int int ScriptBlock 10.03 8.14 10.27 9.48
int int Filter 8.97 9.04 7.33 8.45
int List<int> For 15.10 12.45 13.04 13.53
int List<int> Foreach 9.94 9.86 8.30 9.37
int List<int> Foreach-Object 45.20 41.35 44.35 43.63
int List<int> ScriptBlock 12.36 10.13 10.42 10.97
int List<int> Filter 12.11 10.80 9.74 10.88
int ArrayList<int> For 9.13 9.46 11.98 10.19
int ArrayList<int> Foreach 6.23 6.95 6.35 6.51
int ArrayList<int> Foreach-Object 39.64 40.04 37.64 39.11
int ArrayList<int> ScriptBlock 7.65 7.15 7.17 7.32
int ArrayList<int> Filter 6.92 7.19 9.17 7.76

 

Measured in miliseconds Collection length: 10000
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 105.29 100.37 97.21 100.95
int int Foreach 68.30 66.19 65.25 66.58
int int Foreach-Object 386.23 367.49 377.58 377.10
int int ScriptBlock 87.87 80.90 83.52 84.10
int int Filter 84.34 82.42 80.99 82.58
int List<int> For 132.17 125.16 121.24 126.19
int List<int> Foreach 90.14 88.64 87.66 88.81
int List<int> Foreach-Object 425.56 427.77 417.71 423.68
int List<int> ScriptBlock 112.23 107.41 108.86 109.50
int List<int> Filter 102.32 143.87 104.03 116.74
int ArrayList<int> For 102.66 94.42 97.98 98.35
int ArrayList<int> Foreach 63.03 62.88 63.24 63.05
int ArrayList<int> Foreach-Object 374.20 369.76 376.02 373.33
int ArrayList<int> ScriptBlock 86.63 80.63 80.79 82.68
int ArrayList<int> Filter 75.81 76.07 76.78 76.22

 

Measured in miliseconds Collection length: 100000
Data Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 928.67 989.31 934.40 950.79
int int Foreach 639.67 655.97 648.20 647.95
int int Foreach-Object 3739.28 3814.27 3730.59 3761.38
int int ScriptBlock 817.40 820.79 808.26 815.49
int int Filter 818.23 825.11 838.87 827.41
int List<int> For 1210.98 1221.01 1198.10 1210.03
int List<int> Foreach 843.85 843.32 879.60 855.59
int List<int> Foreach-Object 4280.31 4326.06 4254.13 4286.83
int List<int> ScriptBlock 1075.82 1105.29 1072.61 1084.57
int List<int> Filter 1060.23 1081.08 1071.59 1070.97
int ArrayList<int> For 941.65 937.21 946.71 941.86
int ArrayList<int> Foreach 622.43 593.67 589.76 601.95
int ArrayList<int> Foreach-Object 3574.23 3654.80 3646.56 3625.19
int ArrayList<int> ScriptBlock 765.73 818.17 776.87 786.92
int ArrayList<int> Filter 759.23 747.40 743.89 750.17

 

Measured in miliseconds Collection length: 1000000
Data   Type Collection Type Loop Type Test 1 Test 2 Test 3 Average
int int For 9744.00 9438.96 9758.17 9647.04
int int Foreach 6766.58 6401.43 6640.43 6602.81
int int Foreach-Object 38629.84 37763.99 38132.47 38175.43
int int ScriptBlock 8480.02 8277.16 8329.73 8362.30
int int Filter 8326.80 8178.34 8076.98 8194.04
int List<int> For 12840.89 12497.00 12669.28 12669.06
int List<int> Foreach 8913.08 8733.64 8840.35 8829.02
int List<int> Foreach-Object 43677.32 42700.12 43606.07 43327.84
int List<int> ScriptBlock 11180.41 10921.40 11042.95 11048.25
int List<int> Filter 10552.67 10176.99 10198.66 10309.44
int ArrayList<int> For 9361.94 9696.00 9582.81 9546.92
int ArrayList<int> Foreach 6066.82 6069.82 6185.74 6107.46
int ArrayList<int> Foreach-Object 36339.03 36905.48 37547.94 36930.82
int ArrayList<int> ScriptBlock 7704.85 7682.98 7671.94 7686.59
int ArrayList<int> Filter 7319.12 7521.99 7445.32 7428.81

 

Codes Used to Run the Test

Mesure-Object was sat in each function to measure time. I know It should take out of function if you want to measure CAST cost. Declare Filter:

filter Get-FilterTest{

        [decimal]$total += $_
}

Sample of For Loop Test:

function Get-intForTest{

    param(
    $int
    )

    Measure-Command{
        for ($i = 0; $i -lt $int.length; $i++)
        {
            [decimal]$total += $int[$i]
        }
    }

}

Sample of Foreach Loop Test:

function Get-intForeachTest{

    param(
    $int
    )

    Measure-Command{
        foreach ($i in $int)
        {
            [decimal]$total += $i
        }
    }

}

Sample of Foreach-Object Loop Test:

function Get-intForeachObjectTest{

    param(
    $int
    )

    Measure-Command{
        $int | ForEach-Object{
            [decimal]$total += $_
        }
    }

}

Sample of ScriptBlock Loop Test:

function Get-intScriptBlockTest{

    param(
    $int
    )

    Measure-Command{
        $int | &{process{[decimal]$total += $_}}
    }

}

Sample of Filter Loop Test:

function Get-intFilterTest{

    param(
    $int
    )

    Measure-Command{
        $int | Get-FilterTest
    }
}

Declare Collection.

[int[]]$array=@()
$array += 1..30000000

Run code.

(Get-intForTest -int $array).TotalMilliseconds
(Get-intForeachTest -int $array).TotalMilliseconds
(Get-intForeachObjectTest -int $array).TotalMilliseconds
(Get-intScriptBlockTest -int $array).TotalMilliseconds
(Get-intFilterTest -int $array).TotalMilliseconds
(Get-intFilterTest -int $array).TotalMilliseconds

Here's whole code, it run 4 time to measure.

filter Get-FilterTest{

        [decimal]$total += $_
}

function Get-intForTest{

    param(
    $int
    )

    Measure-Command{
        for ($i = 0; $i -lt $int.length; $i++)
        {
            [decimal]$total += $int[$i]
        }
    }

}

function Get-intForeachTest{

    param(
    $int
    )

    Measure-Command{
        foreach ($i in $int)
        {
            [decimal]$total += $i
        }
    }

}

function Get-intForeachObjectTest{

    param(
    $int
    )

    Measure-Command{
        $int | ForEach-Object{
            [decimal]$total += $_
        }
    }

}

function Get-intScriptBlockTest{

    param(
    $int
    )

    Measure-Command{
        $int | &{process{[decimal]$total += $_}}
    }

}

function Get-intFilterTest{

    param(
    $int
    )

    Measure-Command{
        $int | Get-FilterTest
    }
}

function Get-ListForTest{

	param(
	$int
	)

	$list  = New-Object 'System.Collections.Generic.List`1[System.String]'

	$int | foreach-Object { $list.Add($_)}

	#,$list

    Measure-Command{
	    for ($i = 0; $i -lt $int.length; $i++)
        {
    	    [decimal]$total += $list[$i]
        }
    }
}

function Get-ListForeachTest{

	param(
	$int
	)

	$list  = New-Object 'System.Collections.Generic.List`1[System.String]'

	$int | foreach-Object { $list.Add($_)}

	#,$list

    Measure-Command{
	    foreach ($l in $list)
        {
    	    [decimal]$total += $l
        }
    }
}

function Get-ListForeachObjectTest{

	param(
	$int
	)

	$list  = New-Object 'System.Collections.Generic.List`1[System.String]'

	$int | foreach-Object { $list.Add($_)}

	#,$list

    Measure-Command{
	    $list | foreach-Object{
    	    [decimal]$total += $_
        }
    }
}

function Get-ListScriptBlockTest{

	param(
	$int
	)

	$list  = New-Object 'System.Collections.Generic.List`1[System.String]'

	$int | foreach-Object { $list.Add($_)}

	#,$list

    Measure-Command{
	    $list | &{process{[decimal]$total += $_}}
    }
}

function Get-ListFilterTest{
    param(
    $int
    )

	$list  = New-Object 'System.Collections.Generic.List`1[System.String]'

	$int | foreach-Object { $list.Add($_)}

	#,$list

    Measure-Command{
        $list | Get-FilterTest
    }
}

function Get-ArrayListForTest{
    param(
    $int
    )

	$arrayList = New-Object System.Collections.ArrayList

	[Void]($int | foreach-Object { $arrayList.Add($_) })

	#,$Arraylist

    Measure-Command{
	    for ($i = 0; $i -lt $int.length; $i++)
        {
    	    [decimal]$total += $arrayList[$i]
        }
    }
}

function Get-ArrayListForeachTest{
    param(
    $int
    )

	$arrayList = New-Object System.Collections.ArrayList

	[Void]($int | foreach-Object { $arrayList.Add($_) })

	#,$Arraylist

    Measure-Command{
	    foreach ($al in $arrayList)
        {
    	    [decimal]$total += $al
        }
    }
}

function Get-ArrayListForeachObjectTest{
    param(
    $int
    )

	$arrayList = New-Object System.Collections.ArrayList

	[Void]($int | foreach-Object { $arrayList.Add($_) })

	#,$Arraylist

    Measure-Command{
	    $arrayList | Foreach-Object{
    	    [decimal]$total += $_
        }
    }
}

function Get-ArrayListScriptBlockTest{
    param(
    $int
    )

	$arrayList = New-Object System.Collections.ArrayList

	[Void]($int | foreach-Object { $arrayList.Add($_) })

	#,$Arraylist

    Measure-Command{
	    $arrayList | &{process{[decimal]$total += $_}}
    }
}

filter Get-ArrayListFilterTest{
    param(
    $int
    )

	$arrayList = New-Object System.Collections.ArrayList

	[Void]($int | foreach-Object { $arrayList.Add($_) })

	#,$Arraylist
	Measure-Command {
	    $arrayList | Get-FilterTest
    }
}

[int[]]$array=@()
$array += 1..30000000

1..4 | ForEach-Object {
(Get-intForTest -int $array).TotalMilliseconds
(Get-intForeachTest -int $array).TotalMilliseconds
(Get-intForeachObjectTest -int $array).TotalMilliseconds
(Get-intScriptBlockTest -int $array).TotalMilliseconds
(Get-intFilterTest -int $array).TotalMilliseconds

(Get-ListForTest -int $array).TotalMilliseconds
(Get-ListForeachTest -int $array).TotalMilliseconds
(Get-ListForeachObjectTest -int $array).TotalMilliseconds
(Get-ListScriptBlockTest -int $array).TotalMilliseconds
(Get-ListFilterTest -int $array).TotalMilliseconds

(Get-ArrayListForTest -int $array).TotalMilliseconds
(Get-ArrayListForeachTest -int $array).TotalMilliseconds
(Get-ArrayListForeachObjectTest -int $array).TotalMilliseconds
(Get-ArrayListScriptBlockTest -int $array).TotalMilliseconds
(Get-ArrayListFilterTest -int $array).TotalMilliseconds
""
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)