Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Significant performance difference between x += y and x = x + y on properties, differing between hardware and runtime version (7 / 8) #108227

Open
@BlackxSnow

Description

@BlackxSnow

(I originally detailed this issue on StackOverflow, here.)

Description

The following two snippets produce wildly different benchmark results to eachother as well as between different machines and major runtime versions (where SomeProperty is an int auto-property):

SomeProperty += i;
var propertyValue = SomeProperty;
SomeProperty = propertyValue + i;

The benchmark (below), when run on my machine, showed poor performance of the former case on .NET 7 but otherwise expected results. 2 others ran the benchmarks, resulting in poor performance for both cases on .NET 8 but not .NET 7. The host version did not appear to make a difference in these cases. I've included the benchmark results and system configurations below the benchmark code.

Potentially relevantly (but not directly related), I've noticed (but not been able to isolate) significant performance issues with setting data through a native memory pointer provided by mapping a Direct3D sub-resource which wasn't present on .NET 8 or any of my colleague's machines on .NET 7. That issue appears to be more strongly linked to number of assignments to the pointer than to the amount of data assigned.

Benchmark

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;

namespace Benchmarks;

[DisassemblyDiagnoser(maxDepth: 1, printSource:true)]
[Config(typeof(Config))]
public class FieldVsProperty
{
    public int Prop_ReadWrite { get; set; } = Random.Shared.Next();

    public static int N = 1000;
    
    [Benchmark]
    public int Property_ReadWrite_Write_Add()
    {
        for (int i = 0; i < N; i++)
        {
            Prop_ReadWrite += i;
        }
        return Prop_ReadWrite;
    }
    [Benchmark]
    public int Property_ReadWrite_Write_Add_Separate()
    {
        for (int i = 0; i < N; i++)
        {
            var val = Prop_ReadWrite;
            Prop_ReadWrite = val + i;
        }
        return Prop_ReadWrite;
    }
    
    private class Config : ManualConfig
    {
        public Config()
        {
            AddJob(Job.Default.WithRuntime(CoreRuntime.Core70));
            AddJob(Job.Default.WithRuntime(CoreRuntime.Core80));
        }
    }
}

Data

My machine (also ran this on my Arch Linux install, with no notable difference):

BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4412/22H2/2022Update)
AMD Ryzen 9 7900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK 8.0.302
  [Host]     : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI
  Job-XRUBGY : .NET 7.0.15 (7.0.1523.57226), X64 RyuJIT AVX2
  Job-TPIWHS : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX-512F+CD+BW+DQ+VL+VBMI


| Method                                | Runtime  | Mean       | Error   | StdDev  | Code Size |
|-------------------------------------- |--------- |-----------:|--------:|--------:|----------:|
| Property_ReadWrite_Write_Add          | .NET 7.0 | 1,352.2 ns | 5.95 ns | 5.57 ns |      33 B |
| Property_ReadWrite_Write_Add_Separate | .NET 7.0 |   186.9 ns | 0.53 ns | 0.50 ns |      33 B |
| Property_ReadWrite_Write_Add          | .NET 8.0 |   186.8 ns | 0.45 ns | 0.40 ns |      25 B |
| Property_ReadWrite_Write_Add_Separate | .NET 8.0 |   186.9 ns | 0.40 ns | 0.35 ns |      25 B |

The two other machines:

BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4037/23H2/2023Update/SunValley3)
12th Gen Intel Core i9-12900HK, 1 CPU, 20 logical and 14 physical cores
.NET SDK 8.0.302
  [Host]     : .NET 7.0.16 (7.0.1624.6629), X64 RyuJIT AVX2
  Job-TVXXNG : .NET 7.0.16 (7.0.1624.6629), X64 RyuJIT AVX2
  Job-YOOWAN : .NET 8.0.6 (8.0.624.26715), X64 RyuJIT AVX2


| Method                                | Runtime  | Mean       | Error   | StdDev  | Code Size |
|-------------------------------------- |--------- |-----------:|--------:|--------:|----------:|
| Property_ReadWrite_Write_Add          | .NET 7.0 |   284.5 ns | 5.59 ns | 8.19 ns |      33 B |
| Property_ReadWrite_Write_Add_Separate | .NET 7.0 |   256.1 ns | 3.04 ns | 2.54 ns |      33 B |
| Property_ReadWrite_Write_Add          | .NET 8.0 | 1,488.2 ns | 6.57 ns | 5.49 ns |      25 B |
| Property_ReadWrite_Write_Add_Separate | .NET 8.0 | 1,496.4 ns | 7.92 ns | 7.02 ns |      25 B |
BenchmarkDotNet v0.14.0, Windows 11 (10.0.22631.3880/23H2/2023Update/SunValley3)
12th Gen Intel Core i7-12650H, 1 CPU, 16 logical and 10 physical cores
.NET SDK 8.0.303
  [Host]     : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2
  Job-WMSFHF : .NET 7.0.20 (7.0.2024.26716), X64 RyuJIT AVX2
  Job-HBRVHQ : .NET 8.0.7 (8.0.724.31311), X64 RyuJIT AVX2

| Method                                | Runtime  | Mean       | Error    | StdDev   | Code Size |
|-------------------------------------- |--------- |-----------:|---------:|---------:|----------:|
| Property_ReadWrite_Write_Add          | .NET 7.0 |   407.7 ns |  8.21 ns | 16.40 ns |      33 B |
| Property_ReadWrite_Write_Add_Separate | .NET 7.0 |   348.1 ns |  6.68 ns |  7.70 ns |      33 B |
| Property_ReadWrite_Write_Add          | .NET 8.0 | 1,878.6 ns | 36.54 ns | 51.22 ns |      25 B |
| Property_ReadWrite_Write_Add_Separate | .NET 8.0 | 1,870.5 ns | 37.06 ns | 42.68 ns |      25 B |

Analysis

The most notable difference is between the CPU vendors, but the data is pretty limited.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions