List: Count property is 60x faster than Count method




Date Added (UTC):

25 Apr 2024 @ 16:21

Date Updated (UTC):

25 Apr 2024 @ 16:22


.NET Version(s):

.NET 8

Tag(s):

#Collections #LINQ


Added By:
Profile Image

Blog   
Bangkok, Thailand    
Microsoft MVP | A Developer

Benchmark Results:





Benchmark Code:



using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Jobs;

namespace Benchmarks;

[MemoryDiagnoser(false)]
[HideColumns(Column.RatioSD, Column.AllocRatio)]
[SimpleJob(RuntimeMoniker.Net80)]
public class ListCountBenchmark
{
    private List<int> _list;

    [Params(1_000_000)]
    public int Count { get; set; }

    [GlobalSetup]
    public void GlobalSetup()
    {
        IEnumerable<int> range = Enumerable.Range(0, Count);

        _list = range.ToList();
    }

    [Benchmark]
    public int ListCountMethod() => _list.Count();

    [Benchmark]
    public int ListCountProperty() => _list.Count;

}

// .NET 8
public int ListCountMethod()
{
    return Enumerable.Count(_list);
}
// .NET 8
public int ListCountProperty()
{
    return _list.Count;
}

// .NET 8
.method public hidebysig 
    instance int32 ListCountMethod () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 24 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x208a
    // Code size 12 (0xc)
    .maxstack 8

    // sequence point: (line 37, col 37) to (line 37, col 50) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> Benchmarks.ListCountBenchmark::_list
    IL_0006: call int32 [System.Linq]System.Linq.Enumerable::Count<int32>(class [System.Runtime]System.Collections.Generic.IEnumerable`1<!!0>)
    IL_000b: ret
}
// .NET 8
.method public hidebysig 
    instance int32 ListCountProperty () cil managed 
{
    .custom instance void [BenchmarkDotNet.Annotations]BenchmarkDotNet.Attributes.BenchmarkAttribute::.ctor(int32, string) = (
        01 00 27 00 00 00 01 5f 00 00
    )
    // Method begins at RVA 0x2097
    // Code size 12 (0xc)
    .maxstack 8

    // sequence point: (line 40, col 39) to (line 40, col 50) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Collections]System.Collections.Generic.List`1<int32> Benchmarks.ListCountBenchmark::_list
    IL_0006: callvirt instance int32 class [System.Collections]System.Collections.Generic.List`1<int32>::get_Count()
    IL_000b: ret
}

// .NET 8 (X64)
ListCountMethod()
    L0000: mov rcx, [rcx+8]
    L0004: jmp qword ptr [0x7fff289d7cc0]
// .NET 8 (X64)
ListCountProperty()
    L0000: mov rax, [rcx+8]
    L0004: mov eax, [rax+0x10]
    L0007: ret


Benchmark Description:


For List it's always better to use Count property over Count method (which is using Linq)

The provided benchmark code is designed to measure and compare the performance of two different ways to obtain the count of elements in a `List<int>` in .NET. This setup is specifically targeting .NET 8.0, as indicated by the `RuntimeMoniker.Net80` attribute. The setup involves using BenchmarkDotNet, a powerful .NET library for benchmarking, to conduct these measurements. Below is an overview of the setup and the rationale behind each benchmark method. ### General Setup Overview - **.NET Version**: The benchmarks are configured to run on .NET 8.0, as specified by the `[SimpleJob(RuntimeMoniker.Net80)]` attribute. This ensures that the benchmarks are run using the runtime and optimizations available in .NET 8.0. - **Memory Diagnoser**: The `[MemoryDiagnoser(false)]` attribute is used to disable the collection of detailed memory allocation information. This is likely because the focus of these benchmarks is on execution speed rather than memory usage. - **Column Hiding**: The `[HideColumns(Column.RatioSD, Column.AllocRatio)]` attribute hides specific columns in the output that relate to the standard deviation of the ratio and the allocation ratio, simplifying the results to focus on the primary metrics of interest. - **List Setup**: The `GlobalSetup` method initializes a `List<int>` with a number of elements specified by the `Count` property, which is set to 1,000,000. This setup is performed once before the benchmarks run, ensuring that each benchmark method operates on the same data. ### Benchmark Methods #### 1. `ListCountMethod()` - **Purpose**: This method measures the performance of obtaining the count of elements in a `List<int>` using the `.Count()` extension method from LINQ (`System.Linq`). - **Rationale**: The `.Count()` method is a LINQ operation that can work on any `IEnumerable<T>`, not just lists. It's important to measure its performance because, depending on the implementation, it might enumerate the entire collection to count the elements, which can be less efficient than accessing a property directly. - **Expected Insights**: Since `.Count()` is a method call that might involve more overhead than directly accessing a property, we might expect this approach to be slower, especially for large collections. The benchmark will quantify this difference. #### 2. `ListCountProperty()` - **Purpose**: This method measures the performance of obtaining the count of elements in a `List<int>` directly via the `.Count` property. - **Rationale**: Accessing the `.Count` property of a list is a direct operation that should be very fast because it simply returns the value of an internal field in the `List<T>` class. This benchmark aims to quantify that performance. - **Expected Insights**: Accessing the `.Count` property is expected to be significantly faster than using the `.Count()` method, as it avoids the overhead of method invocation and, in the case of non-List collections, potentially enumerating the entire collection. The results should show a clear performance advantage for the `.Count` property access. ### Conclusion By running these benchmarks, developers can gain insights into the performance implications of different ways to obtain the count of elements in a collection in .NET. Understanding these differences is crucial for writing high-performance code, especially in scenarios where such operations are performed frequently or on large collections. The benchmarks are designed to highlight the efficiency of direct property access versus method calls, a common consideration in performance-critical applications.


Benchmark Comments: