LINQ Sum() performance improvements in .NET8 due to Vectorization




Date Added (UTC):

08 Apr 2024 @ 02:16

Date Updated (UTC):

11 Apr 2024 @ 02:00


.NET Version(s):

.NET 7 .NET 8

Tag(s):

#.Net8PerfImprovement #Collections #LINQ #Vectorization


Added By:
Profile Image

Blog   
Ireland    
.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;
using System.Collections.Generic;
using System.Linq;

[Config(typeof(Config))]
[HideColumns("Error", "StdDev", "Median", "RatioSD", "EnvironmentVariables", "Runtime")]
public class LinqSumBenchmark
{
    private readonly IEnumerable<int> _values = Enumerable.Range(0, 1024).ToArray();

    [Benchmark]
    public int Sum() => _values.Sum();

    private class Config : ManualConfig
    {
        public Config()
        {
            AddJob(Job.Default.WithId(".NET 7").WithRuntime(CoreRuntime.Core70).AsBaseline());
            AddJob(Job.Default.WithId(".NET 8").WithRuntime(CoreRuntime.Core80));

            SummaryStyle =
                SummaryStyle.Default.WithRatioStyle(RatioStyle.Percentage);
        }
    }
}

// .NET 7, .NET 8
public int Sum()
{
    return Enumerable.Sum(_values);
}

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

    // sequence point: (line 28, col 25) to (line 28, col 38) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32> LinqSumBenchmark::_values
    IL_0006: call int32 [System.Linq]System.Linq.Enumerable::Sum(class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>)
    IL_000b: ret
}

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

    // sequence point: (line 28, col 25) to (line 28, col 38) in _
    IL_0000: ldarg.0
    IL_0001: ldfld class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32> LinqSumBenchmark::_values
    IL_0006: call int32 [System.Linq]System.Linq.Enumerable::Sum(class [System.Runtime]System.Collections.Generic.IEnumerable`1<int32>)
    IL_000b: ret
}

// .NET 7 (X64)
Sum()
    L0000: mov rcx, [rcx+8]
    L0004: jmp qword ptr [0x7ffbd5919858]
// .NET 8 (X64)
Sum()
    L0000: mov rcx, [rcx+8]
    L0004: jmp qword ptr [0x7ffdefe84348]


Benchmark Description:


[PR](https://github.com/dotnet/runtime/pull/84519)

### General Setup Overview The benchmark setup provided is designed to measure and compare the performance of a LINQ `Sum` operation across different .NET runtime versions, specifically .NET 7 and .NET 8. It uses BenchmarkDotNet, a powerful .NET library for benchmarking, to conduct the tests and generate reports on the performance metrics. - **.NET Versions**: The benchmark configuration specifies two different .NET Core runtimes: Core 7.0 (as the baseline) and Core 8.0. This allows for a direct comparison of how performance improvements or regressions might have occurred between these two versions regarding the LINQ `Sum` operation on an integer array. - **Configuration**: The `Config` class extends `ManualConfig` from BenchmarkDotNet to customize the benchmarking job. It adds two jobs, one for each .NET runtime version, and sets the .NET 7 job as the baseline for comparisons. It also customizes the `SummaryStyle` to display ratios in percentage format, making it easier to understand performance differences. - **Columns**: The benchmark class is annotated to hide specific columns in the output (`Error`, `StdDev`, `Median`, `RatioSD`, `EnvironmentVariables`, `Runtime`), focusing the report on the most relevant data for this comparison. ### Benchmark Method: `Sum` #### Purpose The `Sum` method is designed to benchmark the performance of the LINQ `Sum` extension method when used to sum a sequence of integers. This is a common operation in many applications, making it a valuable aspect of performance to measure. #### Performance Aspect Being Tested This benchmark specifically tests how efficiently the .NET runtime can iterate over a collection of integers and calculate their sum. It measures the time it takes to perform this operation, which includes the overhead of method invocation, enumeration of the collection, and the arithmetic operation of addition. #### Importance Understanding the performance of the `Sum` method is crucial because: - It's widely used in data processing, analytics, and aggregation tasks. - Performance improvements or regressions in this area can have a significant impact on applications that process large datasets. - It helps developers make informed decisions about which .NET runtime version to target based on performance considerations. #### Expected Results or Insights From running these benchmarks, you should expect to gain insights into: - The absolute performance of the LINQ `Sum` operation on a fixed-size integer array across different .NET runtime versions. - The relative performance improvement or regression from .NET 7 to .NET 8 for this specific operation. - How the .NET runtime optimizations over versions affect common LINQ operations like `Sum`. By analyzing the results, developers can better understand the impact of upgrading to a newer .NET version on the performance of their applications, especially those that heavily rely on LINQ for data processing.


Benchmark Comments: