Creating lists with regular v collection expression approach in .NET 8
Date Added (UTC):
05 Apr 2024 @ 03:44
Date Updated (UTC):05 Apr 2024 @ 03:44
.NET Version(s): Tag(s):
Added By:
.NET Developer and tech lead from Ireland!
Benchmark Results:
Benchmark Code:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
[Config(typeof(Config))]
[HideColumns(Column.Job, Column.RatioSD, Column.AllocRatio)]
[MemoryDiagnoser]
[ReturnValueValidator(failOnError: true)]
public class CollectionExpressions
{
[Benchmark(Baseline = true)]
public List<string> RegularList()
{
return new List<string> { "apple", "banana", "orange" };
}
[Benchmark]
public List<string> ColExpressionList()
{
return ["apple", "banana", "orange"];
}
private class Config : ManualConfig
{
public Config()
{
AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80));
SummaryStyle =
SummaryStyle.Default.WithRatioStyle(RatioStyle.Trend);
}
}
}
Powered by SharpLab
// .NET 8
public List<string> RegularList()
{
List<string> list = new List<string>();
list.Add("apple");
list.Add("banana");
list.Add("orange");
return list;
}
// .NET 8
public List<string> ColExpressionList()
{
List<string> list = new List<string>();
CollectionsMarshal.SetCount(list, 3);
Span<string> span = CollectionsMarshal.AsSpan(list);
int num = 0;
span[num] = "apple";
num++;
span[num] = "banana";
num++;
span[num] = "orange";
num++;
return list;
}
Powered by SharpLab
// .NET 8
.method public hidebysig
instance class [System.Collections]System.Collections.Generic.List`1<string> RegularList () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 19 00 00 00 01 5f 01 00 54 02 08 42 61 73
65 6c 69 6e 65 01
)
// Method begins at RVA 0x2050
// Code size 39 (0x27)
.maxstack 8
// sequence point: (line 28, col 9) to (line 28, col 65) in _
IL_0000: newobj instance void class [System.Collections]System.Collections.Generic.List`1<string>::.ctor()
IL_0005: dup
IL_0006: ldstr "apple"
IL_000b: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_0010: dup
IL_0011: ldstr "banana"
IL_0016: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_001b: dup
IL_001c: ldstr "orange"
IL_0021: callvirt instance void class [System.Collections]System.Collections.Generic.List`1<string>::Add(!0)
IL_0026: ret
}
// .NET 8
.method public hidebysig
instance class [System.Collections]System.Collections.Generic.List`1<string> ColExpressionList () cil managed
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (
01 00 01 00 00
)
.custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
01 00 1f 00 00 00 01 5f 00 00
)
// Method begins at RVA 0x2078
// Code size 76 (0x4c)
.maxstack 3
.locals init (
[0] valuetype [System.Runtime]System.Span`1<string>,
[1] int32
)
// sequence point: (line 34, col 9) to (line 34, col 46) in _
IL_0000: newobj instance void class [System.Collections]System.Collections.Generic.List`1<string>::.ctor()
IL_0005: dup
IL_0006: ldc.i4.3
IL_0007: call void [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::SetCount<string>(class [System.Collections]System.Collections.Generic.List`1<!!0>, int32)
IL_000c: dup
IL_000d: call valuetype [System.Runtime]System.Span`1<!!0> [System.Runtime.InteropServices]System.Runtime.InteropServices.CollectionsMarshal::AsSpan<string>(class [System.Collections]System.Collections.Generic.List`1<!!0>)
IL_0012: stloc.0
IL_0013: ldc.i4.0
IL_0014: stloc.1
IL_0015: ldloca.s 0
IL_0017: ldloc.1
IL_0018: call instance !0& valuetype [System.Runtime]System.Span`1<string>::get_Item(int32)
IL_001d: ldstr "apple"
IL_0022: stind.ref
IL_0023: ldloc.1
IL_0024: ldc.i4.1
IL_0025: add
IL_0026: stloc.1
IL_0027: ldloca.s 0
IL_0029: ldloc.1
IL_002a: call instance !0& valuetype [System.Runtime]System.Span`1<string>::get_Item(int32)
IL_002f: ldstr "banana"
IL_0034: stind.ref
IL_0035: ldloc.1
IL_0036: ldc.i4.1
IL_0037: add
IL_0038: stloc.1
IL_0039: ldloca.s 0
IL_003b: ldloc.1
IL_003c: call instance !0& valuetype [System.Runtime]System.Span`1<string>::get_Item(int32)
IL_0041: ldstr "orange"
IL_0046: stind.ref
IL_0047: ldloc.1
IL_0048: ldc.i4.1
IL_0049: add
IL_004a: stloc.1
IL_004b: ret
}
Powered by SharpLab
|
|
Benchmark Description:
Collection expressions are new in C# 12 / .NET 8. The syntax is more succinct but they might be faster too going by the benchmarks above.
The provided benchmark code is designed to evaluate and compare the performance of two different ways to initialize and return a `List<string>` in C#. It is set up using BenchmarkDotNet, a powerful .NET library for benchmarking, which allows for precise and comprehensive performance measurements. The benchmark is configured to run on .NET 8, showcasing the use of the latest runtime features and improvements at the time of writing.
### General Setup
- **.NET Version:** The benchmark specifies the use of .NET 8 (`CoreRuntime.Core80`), ensuring that the tests run on a specific, cutting-edge runtime environment. This choice is significant because performance characteristics can vary between runtime versions due to optimizations and new features.
- **Configuration:** The benchmark uses a custom configuration class (`Config`) that extends `ManualConfig`. This configuration specifies the use of `.NET 8` and customizes the summary style to focus on trend ratios, which helps in understanding performance changes over time or between different methods.
- **BenchmarkDotNet Attributes:**
- `MemoryDiagnoser`: Activated to report memory allocation statistics, which are crucial for understanding the memory efficiency of the tested methods.
- `ReturnValueValidator`: Ensures that the methods return the correct values, adding a layer of correctness verification to the performance tests.
- `HideColumns`: Hides specific columns in the output report that are not essential for this benchmark, such as Job, Ratio Standard Deviation, and Allocation Ratio, for a cleaner and more focused presentation of results.
### Benchmark Methods
#### 1. `RegularList()`
- **Purpose:** Serves as the baseline for comparison. It initializes a `List<string>` in the traditional way, using the `new` keyword followed by collection initialization syntax.
- **Performance Aspect:** Measures the time and memory allocations involved in creating and initializing a list using the conventional approach. This method is widely used and understood, making it a suitable baseline.
- **Expected Insights:** As the baseline, the performance of this method will be the reference point. The insights here will be about how much more or less efficient alternative methods are compared to this standard approach.
#### 2. `ColExpressionList()`
- **Purpose:** Tests the performance of initializing a `List<string>` using a collection expression (assuming this is a new or hypothetical feature in C#, as it's not standard syntax as of .NET 6).
- **Performance Aspect:** Evaluates whether using a collection expression (simplified syntax for collection initialization) offers any performance benefits or drawbacks compared to the traditional approach. This could include faster execution time, reduced memory allocations, or both.
- **Expected Insights:** This method aims to reveal whether the newer or alternative syntax provides any significant performance improvements or penalties. If the collection expression syntax is more efficient, it could suggest a beneficial language feature for developers focusing on performance. Conversely, if it's less efficient or roughly the same, it might indicate that the traditional syntax is already optimized or that the new syntax offers other benefits (e.g., readability, maintainability) without compromising performance.
### Conclusion
By running these benchmarks, developers can gain valuable insights into the performance implications of different collection initialization techniques in C#. This can guide best practices, especially in performance-critical applications, and contribute to a deeper understanding of how language features and syntax choices affect runtime efficiency.