.NET 7.0中C#属性访问性能较.NET 6.0下降问题咨询
.NET 6迁移至.NET 7/8后结构体访问性能下降问题
问题详情
将项目从.NET 6.0迁移到.NET 7.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; } }
测试结果:
- .NET 6.0下耗时约240ms
- .NET 7.0/.NET 8.0下约450ms(耗时增加约41%)
已确认MSIL代码完全一致,性能分析显示.NET 6.0执行了内联等优化,而.NET 7/8未触发这些优化,推测是RyuJIT编译器的优化策略调整导致。
特殊现象:
- 将结构体改为类,两个版本性能几乎一致
- 给结构体添加额外属性,性能差异消失
- 合并嵌套循环后,性能差异也消失
问题分析
这是.NET 7及后续版本中RyuJIT的优化逻辑变化导致的。针对小型readonly结构体的固定索引访问场景,.NET 6的JIT会将list[0]的访问内联并缓存结构体实例,避免重复从List内部数组读取;而.NET 7/8的JIT在嵌套循环的特定结构下,未触发该缓存优化,导致每次访问结构体字段都要重复读取数组元素,额外增加了内存访问开销。
临时解决方案
- 调整结构体定义:给
readonly结构体添加一个额外的字段/属性(即使是无业务意义的public int Dummy { get; }),打破JIT的特殊判断逻辑,恢复类似.NET 6的优化效果 - 缓存结构体实例:在循环外部提前将
list[0]的结构体实例缓存到局部变量,避免重复访问List索引:static int Main(string[] args) { Stopwatch stopwatch = new Stopwatch(); List<MyStruct> list = new() { new(1, 1)}; var cachedItem = list[0]; // 提前缓存结构体实例 stopwatch.Start(); for (int j = 0; j < 500_000; ++j) { for (int i = 0; i < 1000; ++i) { int p = cachedItem.Parent; int c = cachedItem.Child; } } Console.WriteLine(stopwatch.ElapsedMilliseconds); return 0; } - 切换为类:如果业务场景允许,将
readonly结构体改为类,利用引用类型的访问特性消除性能差异
内容的提问来源于stack exchange,提问作者Markus Ferringer
相关产品推荐
相关产品推荐

