3
A Comparison about all C# For loops
Date Added (UTC):
23 Apr 2024 @ 15:58
Date Updated (UTC):23 Apr 2024 @ 16:37
.NET Version(s): Tag(s):
#.Net8PerfImprovement #.Net9PerfImprovement
Added By:
Senior Software Engineer
Benchmark Results:
Benchmark Code:
Originally imported from :
https://gist.github.com/sa-es-ir/a29b49357e9127493b3ca3138b112b01on 23 Apr 2024 @ 16:37 (UTC) .
The original benchmark may have changed.
https://gist.github.com/sa-es-ir/a29b49357e9127493b3ca3138b112b01
The original benchmark may have changed.
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System.Runtime.InteropServices;
namespace ForLoopsComparison;
[MemoryDiagnoser(false)]
[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.Net80)]
[SimpleJob(RuntimeMoniker.Net90)]
public class AllForLoopBenchmark
{
[Params(100_000)]
public int ItemCount { get; set; }
public List<int> Items;
[GlobalSetup]
public void GlobalSetup()
{
Items = Enumerable.Range(0, ItemCount).ToList();
}
[Benchmark]
public void For()
{
for (int i = 0; i < ItemCount; i++)
{
DoSomeThing(Items[i]);
}
}
[Benchmark]
public void ForEach()
{
foreach (var item in Items)
{
DoSomeThing(item);
}
}
[Benchmark]
public void ForEach_Linq()
{
Items.ForEach(DoSomeThing);
}
[Benchmark]
public void ForEach_Parallel()
{
Parallel.ForEach(Items, DoSomeThing);
}
[Benchmark]
public void ForEach_LinqParallel()
{
Items.AsParallel().ForAll(DoSomeThing);
}
[Benchmark]
public void For_Span()
{
var span = CollectionsMarshal.AsSpan(Items);
for (int i = 0; i < span.Length; i++)
{
DoSomeThing(span[i]);
}
}
[Benchmark]
public void ForEach_Span()
{
foreach (var item in CollectionsMarshal.AsSpan(Items))
{
DoSomeThing(item);
}
}
private void DoSomeThing(int i)
{
_ = i;
}
}
Powered by SharpLab
// .NET 6, .NET 8
public void For()
{
int num = 0;
while (num < ItemCount)
{
DoSomeThing(Items[num]);
num++;
}
}
// .NET 6, .NET 8
public void ForEach()
{
List<int>.Enumerator enumerator = Items.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
int current = enumerator.Current;
DoSomeThing(current);
}
}
finally
{
((IDisposable)enumerator).Dispose();
}
}
// .NET 6, .NET 8
public void ForEach_Linq()
{
Items.ForEach(new Action<int>(DoSomeThing));
}
// .NET 6, .NET 8
public void ForEach_Parallel()
{
Parallel.ForEach((IEnumerable<int>)Items, new Action<int>(DoSomeThing));
}
// .NET 6, .NET 8
public void ForEach_LinqParallel()
{
ParallelEnumerable.ForAll(ParallelEnumerable.AsParallel(Items), new Action<int>(DoSomeThing));
}
// .NET 6, .NET 8
public void For_Span()
{
Span<int> span = CollectionsMarshal.AsSpan(Items);
int num = 0;
while (num < span.Length)
{
DoSomeThing(span[num]);
num++;
}
}
// .NET 6, .NET 8
public void ForEach_Span()
{
Span<int> span = CollectionsMarshal.AsSpan(Items);
int num = 0;
while (num < span.Length)
{
int i = span[num];
DoSomeThing(i);
num++;
}
}
Powered by SharpLab
// .NET 6
.method public hidebysig
instance void For () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 23 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20ac
// Code size 36 (0x24)
.maxstack 3
.locals init (
[0] int32 i
)
// sequence point: (line 38, col 14) to (line 38, col 23) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: hidden
IL_0002: br.s IL_001a
// loop start (head: IL_001a)
// sequence point: (line 40, col 13) to (line 40, col 35) in _
IL_0004: ldarg.0
IL_0005: ldarg.0
IL_0006: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_000b: ldloc.0
IL_000c: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_0011: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 38, col 40) to (line 38, col 43) in _
IL_0016: ldloc.0
IL_0017: ldc.i4.1
IL_0018: add
IL_0019: stloc.0
// sequence point: (line 38, col 25) to (line 38, col 38) in _
IL_001a: ldloc.0
IL_001b: ldarg.0
IL_001c: call instance int32 ForLoopsComparison.AllForLoopBenchmark::get_ItemCount()
IL_0021: blt.s IL_0004
// end loop
// sequence point: (line 42, col 5) to (line 42, col 6) in _
IL_0023: ret
}
// .NET 8
.method public hidebysig
instance void For () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 23 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x207c
// Code size 36 (0x24)
.maxstack 3
.locals init (
[0] int32 i
)
// sequence point: (line 38, col 14) to (line 38, col 23) in _
IL_0000: ldc.i4.0
IL_0001: stloc.0
// sequence point: hidden
IL_0002: br.s IL_001a
// loop start (head: IL_001a)
// sequence point: (line 40, col 13) to (line 40, col 35) in _
IL_0004: ldarg.0
IL_0005: ldarg.0
IL_0006: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_000b: ldloc.0
IL_000c: callvirt instance !0 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Item(int32)
IL_0011: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 38, col 40) to (line 38, col 43) in _
IL_0016: ldloc.0
IL_0017: ldc.i4.1
IL_0018: add
IL_0019: stloc.0
// sequence point: (line 38, col 25) to (line 38, col 38) in _
IL_001a: ldloc.0
IL_001b: ldarg.0
IL_001c: call instance int32 ForLoopsComparison.AllForLoopBenchmark::get_ItemCount()
IL_0021: blt.s IL_0004
// end loop
// sequence point: (line 42, col 5) to (line 42, col 6) in _
IL_0023: ret
}
// .NET 6
.method public hidebysig
instance void ForEach () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 2c 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20dc
// Code size 55 (0x37)
.maxstack 2
.locals init (
[0] valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>,
[1] int32 item
)
// sequence point: (line 47, col 30) to (line 47, col 35) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_000b: stloc.0
.try
{
// sequence point: hidden
IL_000c: br.s IL_001d
// loop start (head: IL_001d)
// sequence point: (line 47, col 18) to (line 47, col 26) in _
IL_000e: ldloca.s 0
IL_0010: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0015: stloc.1
// sequence point: (line 49, col 13) to (line 49, col 31) in _
IL_0016: ldarg.0
IL_0017: ldloc.1
IL_0018: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 47, col 27) to (line 47, col 29) in _
IL_001d: ldloca.s 0
IL_001f: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0024: brtrue.s IL_000e
// end loop
IL_0026: leave.s IL_0036
}
// .NET 8
.method public hidebysig
instance void ForEach () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 2c 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x20ac
// Code size 55 (0x37)
.maxstack 2
.locals init (
[0] valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>,
[1] int32 item
)
// sequence point: (line 47, col 30) to (line 47, col 35) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: callvirt instance valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<!0> class [System.Collections]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_000b: stloc.0
.try
{
// sequence point: hidden
IL_000c: br.s IL_001d
// loop start (head: IL_001d)
// sequence point: (line 47, col 18) to (line 47, col 26) in _
IL_000e: ldloca.s 0
IL_0010: call instance !0 valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0015: stloc.1
// sequence point: (line 49, col 13) to (line 49, col 31) in _
IL_0016: ldarg.0
IL_0017: ldloc.1
IL_0018: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 47, col 27) to (line 47, col 29) in _
IL_001d: ldloca.s 0
IL_001f: call instance bool valuetype [System.Collections]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0024: brtrue.s IL_000e
// end loop
IL_0026: leave.s IL_0036
}
// .NET 6
.method public hidebysig
instance void ForEach_Linq () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 35 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2130
// Code size 24 (0x18)
.maxstack 8
// sequence point: (line 56, col 9) to (line 56, col 36) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: ldarg.0
IL_0007: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_000d: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0012: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::ForEach(class [System.Runtime]System.Action`1<!0>)
// sequence point: (line 57, col 5) to (line 57, col 6) in _
IL_0017: ret
}
// .NET 8
.method public hidebysig
instance void ForEach_Linq () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 35 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2100
// Code size 24 (0x18)
.maxstack 8
// sequence point: (line 56, col 9) to (line 56, col 36) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: ldarg.0
IL_0007: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_000d: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0012: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<int32>::ForEach(class [System.Runtime]System.Action`1<!0>)
// sequence point: (line 57, col 5) to (line 57, col 6) in _
IL_0017: ret
}
// .NET 6
.method public hidebysig
instance void ForEach_Parallel () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 3b 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2149
// Code size 25 (0x19)
.maxstack 8
// sequence point: (line 62, col 9) to (line 62, col 46) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: ldarg.0
IL_0007: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_000d: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0012: call valuetype [System.Threading.Tasks.Parallel]System.Threading.Tasks.ParallelLoopResult [System.Threading.Tasks.Parallel]System.Threading.Tasks.Parallel::ForEach<int32>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Runtime]System.Action`1<!!0>)
IL_0017: pop
// sequence point: (line 63, col 5) to (line 63, col 6) in _
IL_0018: ret
}
// .NET 8
.method public hidebysig
instance void ForEach_Parallel () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 3b 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2119
// Code size 25 (0x19)
.maxstack 8
// sequence point: (line 62, col 9) to (line 62, col 46) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: ldarg.0
IL_0007: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_000d: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0012: call valuetype [System.Threading.Tasks.Parallel]System.Threading.Tasks.ParallelLoopResult [System.Threading.Tasks.Parallel]System.Threading.Tasks.Parallel::ForEach<int32>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Runtime]System.Action`1<!!0>)
IL_0017: pop
// sequence point: (line 63, col 5) to (line 63, col 6) in _
IL_0018: ret
}
// .NET 6
.method public hidebysig
instance void ForEach_LinqParallel () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 41 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2163
// Code size 29 (0x1d)
.maxstack 8
// sequence point: (line 68, col 9) to (line 68, col 48) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0> [System.Linq.Parallel]System.Linq.ParallelEnumerable::AsParallel<int32>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
IL_000b: ldarg.0
IL_000c: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_0012: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0017: call void [System.Linq.Parallel]System.Linq.ParallelEnumerable::ForAll<int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>, class [System.Runtime]System.Action`1<!!0>)
// sequence point: (line 69, col 5) to (line 69, col 6) in _
IL_001c: ret
}
// .NET 8
.method public hidebysig
instance void ForEach_LinqParallel () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 41 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2133
// Code size 29 (0x1d)
.maxstack 8
// sequence point: (line 68, col 9) to (line 68, col 48) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0> [System.Linq.Parallel]System.Linq.ParallelEnumerable::AsParallel<int32>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
IL_000b: ldarg.0
IL_000c: ldftn instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
IL_0012: newobj instance void class [System.Runtime]System.Action`1<int32>::.ctor(object, native int)
IL_0017: call void [System.Linq.Parallel]System.Linq.ParallelEnumerable::ForAll<int32>(class [System.Linq.Parallel]System.Linq.ParallelQuery`1<!!0>, class [System.Runtime]System.Action`1<!!0>)
// sequence point: (line 69, col 5) to (line 69, col 6) in _
IL_001c: ret
}
// .NET 6
.method public hidebysig
instance void For_Span () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 47 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2184
// Code size 46 (0x2e)
.maxstack 3
.locals init (
[0] valuetype [System.Runtime]System.Span`1<int32> span,
[1] int32 i
)
// sequence point: (line 74, col 9) to (line 74, col 53) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_000b: stloc.0
// sequence point: (line 75, col 14) to (line 75, col 23) in _
IL_000c: ldc.i4.0
IL_000d: stloc.1
// sequence point: hidden
IL_000e: br.s IL_0023
// loop start (head: IL_0023)
// sequence point: (line 77, col 13) to (line 77, col 34) in _
IL_0010: ldarg.0
IL_0011: ldloca.s 0
IL_0013: ldloc.1
IL_0014: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_0019: ldind.i4
IL_001a: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 75, col 42) to (line 75, col 45) in _
IL_001f: ldloc.1
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.1
// sequence point: (line 75, col 25) to (line 75, col 40) in _
IL_0023: ldloc.1
IL_0024: ldloca.s 0
IL_0026: call instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
IL_002b: blt.s IL_0010
// end loop
// sequence point: (line 79, col 5) to (line 79, col 6) in _
IL_002d: ret
}
// .NET 8
.method public hidebysig
instance void For_Span () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 47 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2154
// Code size 46 (0x2e)
.maxstack 3
.locals init (
[0] valuetype [System.Runtime]System.Span`1<int32> span,
[1] int32 i
)
// sequence point: (line 74, col 9) to (line 74, col 53) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_000b: stloc.0
// sequence point: (line 75, col 14) to (line 75, col 23) in _
IL_000c: ldc.i4.0
IL_000d: stloc.1
// sequence point: hidden
IL_000e: br.s IL_0023
// loop start (head: IL_0023)
// sequence point: (line 77, col 13) to (line 77, col 34) in _
IL_0010: ldarg.0
IL_0011: ldloca.s 0
IL_0013: ldloc.1
IL_0014: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_0019: ldind.i4
IL_001a: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: (line 75, col 42) to (line 75, col 45) in _
IL_001f: ldloc.1
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.1
// sequence point: (line 75, col 25) to (line 75, col 40) in _
IL_0023: ldloc.1
IL_0024: ldloca.s 0
IL_0026: call instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
IL_002b: blt.s IL_0010
// end loop
// sequence point: (line 79, col 5) to (line 79, col 6) in _
IL_002d: ret
}
// .NET 6
.method public hidebysig
instance void ForEach_Span () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 51 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x21c0
// Code size 48 (0x30)
.maxstack 2
.locals init (
[0] valuetype [System.Runtime]System.Span`1<int32>,
[1] int32,
[2] int32 item
)
// sequence point: (line 84, col 30) to (line 84, col 62) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_000b: stloc.0
IL_000c: ldc.i4.0
IL_000d: stloc.1
// sequence point: hidden
IL_000e: br.s IL_0025
// loop start (head: IL_0025)
// sequence point: (line 84, col 18) to (line 84, col 26) in _
IL_0010: ldloca.s 0
IL_0012: ldloc.1
IL_0013: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_0018: ldind.i4
IL_0019: stloc.2
// sequence point: (line 86, col 13) to (line 86, col 31) in _
IL_001a: ldarg.0
IL_001b: ldloc.2
IL_001c: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: hidden
IL_0021: ldloc.1
IL_0022: ldc.i4.1
IL_0023: add
IL_0024: stloc.1
// sequence point: (line 84, col 27) to (line 84, col 29) in _
IL_0025: ldloc.1
IL_0026: ldloca.s 0
IL_0028: call instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
IL_002d: blt.s IL_0010
// end loop
// sequence point: (line 88, col 5) to (line 88, col 6) in _
IL_002f: ret
}
// .NET 8
.method public hidebysig
instance void ForEach_Span () cil managed
{
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 51 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2190
// Code size 48 (0x30)
.maxstack 2
.locals init (
[0] valuetype [System.Runtime]System.Span`1<int32>,
[1] int32,
[2] int32 item
)
// sequence point: (line 84, col 30) to (line 84, col 62) in _
IL_0000: ldarg.0
IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> ForLoopsComparison.AllForLoopBenchmark::Items
IL_0006: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<int32>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_000b: stloc.0
IL_000c: ldc.i4.0
IL_000d: stloc.1
// sequence point: hidden
IL_000e: br.s IL_0025
// loop start (head: IL_0025)
// sequence point: (line 84, col 18) to (line 84, col 26) in _
IL_0010: ldloca.s 0
IL_0012: ldloc.1
IL_0013: call instance !0& valuetype [System.Runtime]System.Span`1<int32>::get_Item(int32)
IL_0018: ldind.i4
IL_0019: stloc.2
// sequence point: (line 86, col 13) to (line 86, col 31) in _
IL_001a: ldarg.0
IL_001b: ldloc.2
IL_001c: call instance void ForLoopsComparison.AllForLoopBenchmark::DoSomeThing(int32)
// sequence point: hidden
IL_0021: ldloc.1
IL_0022: ldc.i4.1
IL_0023: add
IL_0024: stloc.1
// sequence point: (line 84, col 27) to (line 84, col 29) in _
IL_0025: ldloc.1
IL_0026: ldloca.s 0
IL_0028: call instance int32 valuetype [System.Runtime]System.Span`1<int32>::get_Length()
IL_002d: blt.s IL_0010
// end loop
// sequence point: (line 88, col 5) to (line 88, col 6) in _
IL_002f: ret
}
Powered by SharpLab
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Benchmark Description:
A Comparison about C# For loops: .NET 9 is slower than .NET 8 in some methods
Just out of curiosity, I made this benchmark for checking the performance, and as we know the pure For loops is a winner.
The interesting thing for me was that .NET 9 that was slower than .NET 8 in some methods like ForEach_LinqParallel.
Although these numbers are negligible still fun to know.
The provided benchmark code is designed to compare the performance of different looping constructs in C# across multiple versions of the .NET runtime. It aims to measure the efficiency of iterating over a collection in various ways, focusing on execution time and, indirectly, memory usage (though memory diagnosing is explicitly turned off in this setup). This benchmark setup can reveal insights into how different looping mechanisms perform under the same conditions and how performance might vary across different .NET versions.
### General Setup Overview
- **.NET Versions:** The benchmarks are set to run on .NET 6.0, .NET 8.0, and .NET 9.0, as indicated by the `RuntimeMoniker` attributes. This allows for performance comparison across these runtime versions.
- **Memory Diagnoser:** Disabled (`[MemoryDiagnoser(false)]`), indicating that memory allocation metrics are not being collected.
- **ItemCount Parameter:** The benchmarks are parameterized to run with a collection size of 100,000 items. This provides a substantial workload to measure the performance differences between the loop constructs.
- **Global Setup:** Before each benchmark, a list of integers (`Items`) is populated with 100,000 sequential integers starting from 0. This setup ensures that each benchmark method operates on the same data.
### Benchmark Methods and Rationale
1. **For Loop (`For`):**
- **Purpose:** Measures the performance of a traditional `for` loop accessing elements by index.
- **Importance:** Serves as a baseline for performance comparison. Direct indexing is typically fast but depends on the collection type.
- **Expected Insights:** Generally, direct indexing should be efficient, but the results will show how it compares to other methods.
2. **ForEach Loop (`ForEach`):**
- **Purpose:** Evaluates the performance of the `foreach` loop construct over a `List<T>`.
- **Importance:** `foreach` is widely used for its readability and convenience, but it may introduce overhead compared to index-based access.
- **Expected Insights:** Likely to be slightly slower than direct indexing due to enumerator overhead.
3. **ForEach with LINQ (`ForEach_Linq`):**
- **Purpose:** Tests the performance of using `List<T>.ForEach` with a method delegate.
- **Importance:** This method is often used for its concise syntax but may have performance implications.
- **Expected Insights:** This could introduce additional overhead compared to traditional loops, especially due to delegate invocation.
4. **Parallel.ForEach (`ForEach_Parallel`):**
- **Purpose:** Measures how `Parallel.ForEach` performs with the list, aiming to utilize multiple threads.
- **Importance:** Important for understanding the benefits and overhead of parallel processing on collections.
- **Expected Insights:** Should show significant performance improvement on multi-core systems, but with overhead for coordination between threads.
5. **Parallel LINQ (`ForEach_LinqParallel`):**
- **Purpose:** Evaluates the performance of parallel processing using PLINQ.
- **Importance:** Offers a LINQ-friendly way to parallelize operations, balancing ease of use with performance.
- **Expected Insights:** Similar to `Parallel.ForEach`, but the overhead might vary due to PLINQ's query optimizations.
6. **For Loop with Span (`For_Span`):**
- **Purpose:** Tests the performance of iterating over a `Span<T>` created from the list.
- **Importance:** `Span<T>` is designed for performance-critical scenarios, offering a way to access collections without heap allocations.
- **Expected Insights:** Expected to be among the fastest, especially for value types, due to reduced overhead and memory efficiency.
7. **ForEach Loop with Span (`ForEach_Span`):**
- **Purpose:** Measures the performance of using `foreach` over a `Span<T>`.
- **Importance:** Combines the convenience of `foreach` with the performance benefits of `Span<T>`.
- **Expected Insights:** Likely to offer improved performance over regular `foreach` on lists, but with some overhead compared to a `for` loop with `Span<T>`.
### Conclusion
Running these benchmarks will provide valuable insights into the performance characteristics of different looping constructs in C#. The results will highlight the trade-offs between ease of use, readability, and execution speed, especially across different .NET runtime versions. Understanding these trade-offs is crucial for writing high-performance C# code, particularly in scenarios where performance is critical.