在访问属性方面,.NET 7.0 中的 C# 性能比 .NET 6.0 中的性能差

Mar*_*ger 0 .net c# performance

我从 .NET 6.0 更改为 .NET 7.0,发现一些性能关键型任务所需的时间几乎是 .NET 6.0 的两倍(没有进行任何代码更改!)。

我能够得出一个简单的例子,它至少显示了一个有趣的效果:

using System;
using System.Collections.Generic;
using System.Diagnostics;

public readonly struct MyStruct {
  public int Parent { get; }
  public int Child { get; } 
  public MyStruct(int p, int c) {
    Parent = p;
    Child = c;
  }
}

static class MyProgram {
  
  static int Main(string[] args) {
    Stopwatch stopwatch = new Stopwatch();
    List<MyStruct> list = new() { new(1, 1)};
    stopwatch.Start();
    for (int j = 0; j < 500_000; ++j) {
      for (int i = 0; i < 1000; ++i) {
        int p = list[0].Parent;
        int c = list[0].Child;
      }
    }
    Console.WriteLine(stopwatch.ElapsedMilliseconds);
    return 0;
  }

}
Run Code Online (Sandbox Code Playgroud)

对于 .NET 6.0,这大约需要 240 毫秒,而对于 .NET 7.0,则需要 450 毫秒(大约长 41%)。

据我所知,MSIL 代码是等效的,所以看起来 JIT 编译器在 .NET 7.0 中做了一些严重不同的事情?

任何人都可以确认这一点,或者其他人在切换到 .NET 7.0 时是否会遇到性能下降(顺便说一下,对于 .NET 8.0 来说是一样的)?

这种行为非常脆弱:创建一个结构体类,添加另一个属性,将嵌套循环组合成一个等等,并且两个版本的性能几乎相同。

仪表显示(对于.NET 7.0): 在此输入图像描述

对于 .NET 6.0: 在此输入图像描述

所以它表明 .NET 6.0 做了一些优化(内联?),而在 .NET 7.0 中不再进行这些优化。

mas*_*son 8

你的测试有问题。仅诉诸 Stopwatch 类并在调试模式下运行不会给您带来有意义的结果。还有很多事情要做,比如预热 JITer。使用BenchmarkDotNet等适当的基准测试工具表明 .NET 7 在这方面实际上更快一些,并且内存效率更高。

方法 工作 运行 意思是 错误 标准差 已分配
我的基准测试 .NET 6.0 .NET 6.0 366.3 毫秒 7.24 毫秒 9.15 毫秒 728乙
我的基准测试 .NET 7.0 .NET 7.0 320.0 毫秒 6.09 毫秒 5.40 毫秒 388 乙

MyBenchmarks.cs

using BenchmarkDotNet.Attributes;

namespace HelloConsole;

[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net60)]
[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)]
[MemoryDiagnoser]
public class MyBenchmarks
{
    public readonly struct MyStruct
    {
        public int Parent { get; }
        public int Child { get; }
        public MyStruct(int p, int c)
        {
            Parent = p;
            Child = c;
        }
    }

    [Benchmark]
    public void MyBenchmark()
    {        
        List<MyStruct> list = new() { new(1, 1) };
        for (int j = 0; j < 500_000; ++j)
        {
            for (int i = 0; i < 1000; ++i)
            {
                int p = list[0].Parent;
                int c = list[0].Child;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

程序.cs

using BenchmarkDotNet.Running;
using HelloConsole;

var summary = BenchmarkRunner.Run<MyBenchmarks>();
Run Code Online (Sandbox Code Playgroud)

项目文件(.csproj)

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFrameworks>net6.0;net7.0</TargetFrameworks>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
  </ItemGroup>
</Project>
Run Code Online (Sandbox Code Playgroud)