2025年11月12日,微软在.NET Conf 2025正式发布了.NET 10。 作为一个长期支持(LTS)版本,它将获得为期三年的官方安全补丁与服务支持,直至 2028 年 11 月 10 日, 官方称其为"迄今为止最高效、最现代、最安全、最智能、性能最高的 .NET 版本"。让我们一起来看看.NET 10 的性能相对于上一代版本 .NET 9 有哪些地方的提高吧。
一、.Net10的性能优化
真正的突破来自系统性的微小改进,而非单一的革命性创新。十九世纪"冰王"弗雷德里克通过改良绝缘材料、优化切割工艺和物流体系,使冰块能远渡重洋抵达印度。同样,.NET 10的性能提升并非依靠某个突破性创意,而是通过数百个细微优化------每个节省几纳秒、几十字节------在代码的万亿次执行中产生复合效应。这印证了持续的系统性优化,才是实现质变的关键。
在软件开发领域,性能优化始终是技术演进的核心驱动力之一。.NET 10的发布带来了令人瞩目的性能提升,从LINQ查询到底层容器操作,从委托调用到线程池调度,几乎每个层面都实现了显著的性能突破。.NET 10的优化成果展现了一种全新的性能优化理念------这不再是传统的微观调优,而是对运行时行为的深度重构。从这种重新思考的性能哲学中可以看到从"执行代码"到"理解意图"的跨越、系统架构层面的洞察、技术决策的深层逻辑等几个根本性的转变。
下面我们可以基于部分典型的基准测试数据,深入剖析.NET 10 vs .NET 9 在部分代码、场景下性能优化成果,并揭示其背后的技术原理和设计哲学。
二、Linq优化
LINQ的优化是思维方式转变。传统的查询执行是机械式的:接收一个操作序列,然后按部就班地执行每个步骤。而.NET 10的LINQ实现了语义感知------它能够理解开发者的查询意图,而不是盲目执行操作序列。这种转变类似于一个聪明的数学学生,看到"先排序再查找特定值"时,会直接跳过排序直接搜索。这种基于语义的优化比任何微观调优都更加有效,因为它改变了算法的时间复杂度本身,也代表了编译器从"代码执行者"向"意图理解者"的转变。具体内容参考以下案例:
Benchmark 2.1
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private IEnumerable<int> _source = Enumerable.Range(0, 1000).ToArray();
[Benchmark]
public bool AppendContains() => _source.Append(100).Contains(999);
[Benchmark]
public bool ConcatContains() => _source.Concat(_source).Contains(999);
[Benchmark]
public bool DefaultIfEmptyContains() => _source.DefaultIfEmpty(42).Contains(999);
[Benchmark]
public bool DistinctContains() => _source.Distinct().Contains(999);
[Benchmark]
public bool OrderByContains() => _source.OrderBy(x => x).Contains(999);
[Benchmark]
public bool ReverseContains() => _source.Reverse().Contains(999);
[Benchmark]
public bool UnionContains() => _source.Union(_source).Contains(999);
[Benchmark]
public bool SelectManyContains() => _source.SelectMany(x => _source).Contains(999);
[Benchmark]
public bool WhereSelectContains() => _source.Where(x => true).Select(x => x).Contains(999);
}
| Method | Runtime | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|
| AppendContains | .NET 9.0 | 2,931.97 ns | 1.00 | 88 B | 1.00 |
| AppendContains | .NET 10.0 | 52.06 ns | 0.02 | 56 B | 0.64 |
| ConcatContains | .NET 9.0 | 3,065.17 ns | 1.00 | 88 B | 1.00 |
| ConcatContains | .NET 10.0 | 54.58 ns | 0.02 | 56 B | 0.64 |
| DefaultIfEmptyContains | .NET 9.0 | 39.21 ns | 1.00 | - | NA |
| DefaultIfEmptyContains | .NET 10.0 | 32.89 ns | 0.84 | - | NA |
| DistinctContains | .NET 9.0 | 16,967.31 ns | 1.000 | 58656 B | 1.000 |
| DistinctContains | .NET 10.0 | 46.72 ns | 0.003 | 64 B | 0.001 |
| OrderByContains | .NET 9.0 | 12,884.28 ns | 1.000 | 12280 B | 1.000 |
| OrderByContains | .NET 10.0 | 50.14 ns | 0.004 | 88 B | 0.007 |
| ReverseContains | .NET 9.0 | 479.59 ns | 1.00 | 4072 B | 1.00 |
| ReverseContains | .NET 10.0 | 51.80 ns | 0.11 | 48 B | 0.01 |
| UnionContains | .NET 9.0 | 16,910.57 ns | 1.000 | 58664 B | 1.000 |
| UnionContains | .NET 10.0 | 55.56 ns | 0.003 | 72 B | 0.001 |
| SelectManyContains | .NET 9.0 | 2,950.64 ns | 1.00 | 192 B | 1.00 |
| SelectManyContains | .NET 10.0 | 60.42 ns | 0.02 | 128 B | 0.67 |
| WhereSelectContains | .NET 9.0 | 1,782.05 ns | 1.00 | 104 B | 1.00 |
| WhereSelectContains | .NET 10.0 | 260.25 ns | 0.15 | 104 B | 1.00 |
在.NET 10中,LINQ的Contains方法获得了显著的性能优化。LINQ在各种迭代器特化中新增了近30个专门的Contains实现,使得在OrderBy、Distinct、Reverse等复杂操作后调用Contains时,能够跳过不必要的处理直接搜索源数据。
基准测试结果展现了惊人的改进:DistinctContains性能提升约99.7%,耗时从16,967 ns降至47 ns,内存分配从58,656 B降至64 B;OrderByContains提升约99.6%,从12,884 ns降至50 ns,内存分配从12,280 B降至88 B;ReverseContains提升约89%,从480 ns降至52 ns,内存分配从4,072 B降至48 B。其他操作如AppendContains、ConcatContains等也有类似幅度的提升。
这些优化得益于LINQ内部迭代器类型之间的信息传递机制。每个LINQ方法返回特定的IEnumerable 实现类型,这些类型包含源数据、选择器函数等上下文信息,并重写虚方法以实现操作特化。当Contains检测到前序操作时,会直接搜索原始源而非处理中间结果,避免了昂贵的排序、去重或内存分配操作,实现了从O(N log N)到O(N)的复杂度优化。
三、委托调用优化
委托调用内联的优化是提高函数式编程风格和事件处理效率的核心路径,其技术本质是JIT编译器能精确追踪对象生命周期,可见.NET团队正在努力减少高级语言特性带来运行时开销。具体内容参考以下案例:
Benchmark 3.1
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[DisassemblyDiagnoser]
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD", "y")]
public partial class Tests
{
[Benchmark]
[Arguments(42)]
public int Sum(int y)
{
Func<int, int> addY = x => x + y;
return DoubleResult(addY, y);
}
private int DoubleResult(Func<int, int> func, int arg)
{
int result = func(arg);
return result + result;
}
}
代码中 x => x + y 中的 y 是外部变量,这创建了一个"闭包"。这是性能开销的传统来源,因为需要生成一个类来存储 y。然后这个闭包被转换为一个 Func<int, int> 委托。在这里.NET 10 的 JIT 编译器能够内联 DoubleResult 方法,并发现这个委托并没有被存储或传递出去(没有"逃逸"),因此它完全避免了创建委托对象本身,只分配了那个必不可少的闭包对象。
在.NET 10中,JIT编译器通过完全消除委托分配实现了性能突破:它将原本需要分配88字节(包括闭包对象和委托对象)的内存开销减少到仅需24字节(仅保留闭包对象),同时将复杂的委托调用机制优化为直接的内联代码执行,这使得代码路径从包含多层对象创建和条件跳转的112字节汇编指令简化为仅32字节的直接数学运算,最终让执行时间从19.530纳秒大幅降至6.685纳秒,性能提升达66%。
| Method | Runtime | Mean | Ratio | Code Size | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| Sum | .NET 9.0 | 19.530 ns | 1.00 | 118 B | 88 B | 1.00 |
| Sum | .NET 10.0 | 6.685 ns | 0.34 | 32 B | 24 B | 0.27 |
四、局部函数内联提升
通过生命周期分析,安全转化为栈分配,让开发者无需手动优化就能获得近乎零分配的性能。具体内容参考以下案例:
Benchmark 3.2
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
[Benchmark]
public void Test()
{
Process(new string[] { "a", "b", "c" });
static void Process(string[] inputs)
{
foreach (string input in inputs)
{
Use(input);
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Use(string input) { }
}
}
}
| Method | Runtime | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|
| Test | .NET 9.0 | 11.580 ns | 1.00 | 48 B | 1.00 |
| Test | .NET 10.0 | 3.960 ns | 0.34 | -- | 0.00 |
在.NET 10中,JIT编译器通过内联局部方法并可以分析发现到数组从未离开当前栈帧,因此能将堆上的数组分配优化为栈分配,从而完全消除了48字节的内存分配,同时将执行时间从11.580纳秒大幅减少到3.960纳秒,性能提升达66%。这种优化使得即使在没有ReadOnlySpan 重载的情况下,开发者在使用数组、params参数或集合表达式时也能获得近乎零分配的高性能体验。
四、去虚拟化
问题的根源是.NET 9中数组接口调用无法去虚拟化。而NET10消除了数组在集合封装时的特殊性能瓶颈。具体内容参考以下案例:
Benchmark 4.1
csharp
// dotnet run -c Release -f net9.0 --filter "*"
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.ObjectModel;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private ReadOnlyCollection<int> _list = new(Enumerable.Range(1, 1000).ToArray());
[Benchmark]
public int SumEnumerable()
{
int sum = 0;
foreach (var item in _list)
{
sum += item;
}
return sum;
}
[Benchmark]
public int SumForLoop()
{
ReadOnlyCollection<int> list = _list;
int sum = 0;
int count = list.Count;
for (int i = 0; i < count; i++)
{
sum += _list[i];
}
return sum;
}
}
| Method | Runtime | Mean | Ratio |
|---|---|---|---|
| SumEnumerable | .NET 9.0 | 968.5 ns | 1.00 |
| SumEnumerable | .NET 10.0 | 775.5 ns | 0.80 |
| SumForLoop | .NET 9.0 | 1960.5 ns | 1.00 |
| SumForLoop | .NET 10.0 | 624.6 ns | 0.32 |
在.NET 9中,由于JIT编译器无法对数组的接口实现进行去虚拟化,导致了一个反直觉的性能现象:当ReadOnlyCollection 包装数组时,使用for循环索引访问的性能反而比foreach循环更差。这是因为foreach只需一次GetEnumerator()接口调用,后续的MoveNext()和Current可以被去虚拟化;而for循环中的每次_list[i]索引访问都需要进行接口调用,对1000个元素就产生1000次虚拟调用开销。
.NET 10解决了这一根本问题,使JIT能够对数组的接口方法实现进行完全去虚拟化。优化后,SumForLoop从1932.7纳秒大幅降至624.6纳秒,性能提升68%,真正发挥了直接索引访问的优势;同时SumEnumerable也从949.5纳秒优化到775.5纳秒,提升20%。这一突破消除了数组在接口调用时的特殊性能瓶颈,让代码性能表现更加符合开发者的直观预期。
此外数组接口去虚拟化的优化对 LINQ 的性能也有显著的提升。由于许多 LINQ 操作在底层都依赖于对集合的接口调用(如通过 IList 进行索引访问或通过 IEnumerable 进行枚举),.NET 10 中对数组接口去虚拟化的改进,使得那些以数组为数据源的 LINQ 查询现在能够避免大量的虚拟调用开销,从而获得显著的性能提升。这意味着常见的 LINQ 操作(如 Where、Select、Take 等)在处理数组时,现在能够更高效地执行,使得 LINQ 在性能敏感场景下的实用性进一步增强。
Benchmark 4.2
csharp
public int SkipTakeSum() => _list.Skip(100).Take(800).Sum()
| Method | Runtime | Mean | Ratio |
|---|---|---|---|
| SkipTakeSum | .NET 9.0 | 3.525 us | 1.00 |
| SkipTakeSum | .NET 10.0 | 1.773 us | 0.50 |
五、JIT类型细化
在JIT中,部分抽象接口重新被具体化了,即IEnumerable 重新被识别为实际int[],这种新的机制即保持了编码时的抽象灵活性,获得具体类型的性能。具体内容参考以下案例:
Benchmark 5.1
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private static readonly IEnumerable<int> s_values = new int[] { 1, 2, 3, 4, 5 };
[Benchmark]
public int Sum()
{
int sum = 0;
foreach (int value in s_values)
{
sum += value;
}
return sum;
}
}
| Method | Runtime | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|
| Sum | .NET 9.0 | 16.341 ns | 1.00 | 32 B | 1.00 |
| Sum | .NET 10.0 | 2.059 ns | 0.13 | - | 0.00 |
在这个例子中,即使 s_values 的类型是 IEnumerable ,JIT 编译器能够识别出它实际上总是一个 int[] 数组。借助 .NET 10 的新优化能力,JIT 会在内部将返回类型重新定义为 int[],从而使得枚举器可以在栈上分配。这种类型细化让 JIT 能够绕过接口调用的开销,直接生成针对具体类型的高效代码,即使源代码使用的是抽象接口。
六、线程优化
线程池是大多数.NET应用程序和服务中任务处理的基础组件,它采用多级队列设计来优化性能。通常情况下,每个线程池线程都拥有自己的本地队列,而外部线程提交的任务则进入全局队列。线程在寻找工作时会遵循特定优先级:首先处理自己本地队列中的任务,然后是全局队列中的任务,最后才会尝试从其他线程的本地队列中"窃取"工作。这种设计既能减少全局队列的竞争,又能确保相关任务被优先处理。
然而,当出现"同步包装异步"这种反模式时,系统就会陷入困境。具体来说,当一个线程池线程阻塞等待异步操作完成,而这个异步操作又需要其他线程池线程来处理某些工作项时,就形成了循环依赖。更糟糕的是,被阻塞线程本地队列中的工作项本应是高优先级的,但由于该线程已被阻塞,这些工作项反而变成了最低优先级的任务------只有当全局队列被清空后,其他线程才会来窃取这些工作项。如果系统持续向全局队列注入新工作,这些关键工作项可能永远得不到执行。
.NET 10通过一个巧妙的方法解决了这个问题:当线程即将阻塞等待任务完成时,它会主动将本地队列中的所有工作项转移到全局队列中。这样,这些原本可能被无限期延迟的工作项现在能够被其他空闲线程及时处理,从而打破了潜在的僵局。这个优化确保了即使在出现同步阻塞的情况下,线程池仍能保持高效运转。
这种调度策略的进化,反映了对现实工作负载模式的更深理解------在高并发环境下,避免系统性僵局比优化单个任务的延迟更加重要。
Benchmark 6.1
csharp
// dotnet run -c Release -f net9.0 --filter "*"
// dotnet run -c Release -f net10.0 --filter "*"
using System.Diagnostics;
int numThreads = Environment.ProcessorCount;
ThreadPool.SetMaxThreads(numThreads, 1);
ManualResetEventSlim start = new();
CountdownEvent allDone = new(numThreads);
new Thread(() =>
{
while (true)
{
for (int i = 0; i < 10_000; i++)
{
ThreadPool.QueueUserWorkItem(_ => Thread.SpinWait(1));
}
Thread.Yield();
}
}) { IsBackground = true }.Start();
for (int i = 0; i < numThreads; i++)
{
ThreadPool.QueueUserWorkItem(_ =>
{
start.Wait();
TaskCompletionSource tcs = new();
const int LocalItemsPerThread = 4;
var remaining = LocalItemsPerThread;
for (int j = 0; j < LocalItemsPerThread; j++)
{
Task.Run(() =>
{
Thread.SpinWait(100);
if (Interlocked.Decrement(ref remaining) == 0)
{
tcs.SetResult();
}
});
}
tcs.Task.Wait();
allDone.Signal();
});
}
var sw = Stopwatch.StartNew();
start.Set();
Console.WriteLine(allDone.Wait(20_000) ?
$"Completed: {sw.ElapsedMilliseconds}ms" :
$"Timed out after {sw.ElapsedMilliseconds}ms");
| Runtime | ElapsedTime |
|---|---|
| .NET 9.0 | 超时20s |
| .NET 10.0 | 4 ms |
这段代码模拟了一个典型的线程池死锁场景。它首先创建了一个干扰线程,持续向全局队列中填充大量工作项,以此模拟高并发负载。然后,提交了与处理器核心数相同的主工作项,每个主工作项又会向所在线程的本地队列提交四个子工作项。主工作项会阻塞等待,直到所有子工作项完成。
在.NET 9中运行此代码时会出现超时。这是因为干扰线程产生的持续不断的新工作项充斥了全局队列,导致所有线程池线程都忙于处理这些"噪音"工作,没有机会去窃取其他线程本地队列中的子工作项。由于这些关键的子工作项无法被执行,主工作项也就永远无法被解除阻塞,最终形成死锁。
而在.NET 10中,情况得到了根本性改善。当主工作项即将阻塞等待子任务完成时,线程池会主动将其本地队列中的四个子工作项转移到全局队列中。这样一来,这些关键工作项就不再依赖于工作窃取机制,而是进入了公共的工作流中,能够被任何空闲的线程及时处理。因此,代码能够在毫秒级时间内快速完成,从.Net9超时20秒的超时优化到.Net10仅需4毫秒,展现了新机制解决线程池僵局的卓越效果。
七、UInt128 除法
充分利用现代CPU特性。
Benchmark 7.1
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private UInt128 _n = new UInt128(123, 456);
private UInt128 _d = new UInt128(0, 789);
[Benchmark]
public UInt128 Divide() => _n / _d;
}
| Method | Runtime | Mean | Ratio |
|---|---|---|---|
| Divide | .NET 9.0 | 27.3112 ns | 1.00 |
| Divide | .NET 10.0 | 0.5522 ns | 0.02 |
在 .NET 10 中,UInt128 类型的除法性能得到了显著的硬件级优化。当处理一个大于 ulong(64位)的 UInt128 数值除以一个在 ulong 范围内的值时,.NET 运行时现在会直接利用 X86 平台的 DivRem 硬件内在函数来执行运算。这意味着原本需要通过复杂软件算法模拟的 128 位除法操作,现在可以委托给 CPU 的专用指令直接、高效地完成,从而避免了昂贵的软件模拟开销。这项改进使得 UInt128 的除法操作获得了近 50 倍的性能提升,这对于密码学、大数计算和高性能哈希算法等依赖大整数运算的场景至关重要。
八、Stack、ConcurrentDictionary、Queue容器的遍历优化
长期以来,开发者在抽象层级和运行性能之间面临艰难抉择。.NET 10通过多层次的协同优化,在努力解决这个困境,通过重新设计迭代器的状态机逻辑,将多个条件检查合并为单个边界检查。这不仅仅是减少了几个CPU指令,更是对算法本质的重新思考------用更简单的方式表达相同的逻辑。
Benchmark 8.1
以Stack为例
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(displayGenColumns: false)]
[DisassemblyDiagnoser]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private Stack<int> _direct = new Stack<int>(Enumerable.Range(0, 10));
private IEnumerable<int> _enumerable = new Stack<int>(Enumerable.Range(0, 10));
[Benchmark]
public int SumDirect()
{
int sum = 0;
foreach (int item in _direct) sum += item;
return sum;
}
[Benchmark]
public int SumEnumerable()
{
int sum = 0;
foreach (int item in _enumerable) sum += item;
return sum;
}
}
| Method | Runtime | Mean | Ratio | Code Size | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|
| SumDirect | .NET 9.0 | 23.317 ns | 1.00 | 331 B | - | NA |
| SumDirect | .NET 10.0 | 4.502 ns | 0.19 | 55 B | - | NA |
| SumEnumerable | .NET 9.0 | 30.893 ns | 1.00 | 642 B | 40 B | 1.00 |
| SumEnumerable | .NET 10.0 | 7.906 ns | 0.26 | 381 B | - | 0.00 |
在.NET 10中,Stack 的枚举器实现得到了显著优化。原先的枚举器在每次调用 MoveNext 时需要经过五个条件分支:版本检查、首次调用检查、枚举结束检查、剩余元素检查和数组边界检查。新的实现通过将初始索引设为栈长度并逐次递减的巧妙设计,将这些检查合并为单个边界检查 if ((uint)index < (uint)array.Length),将分支数量从五个减少到两个。
这一优化不仅减少了代码处理量和分支预测错误的风险,还大幅缩小了代码体积,使得枚举器方法更易于内联,进而促进了栈内存分配的可能性。基准测试结果充分展现了优化效果:直接枚举的性能提升了约81%(从23.317纳秒降至4.502纳秒),代码体积从331字节缩减至55字节;而通过IEnumerable接口枚举的性能也提升了约74%(从30.893纳秒降至7.906纳秒),并完全消除了内存分配。这体现了.NET团队通过精简算法和优化底层机制来提升性能的卓越成果。
Queue的比较结果
| Method | Runtime | Mean | Ratio | BranchInstructions/Op | Code Size | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|
| SumDirect | .NET 9.0 | 24.340 ns | 1.00 | 79 | 251 B | - | NA |
| SumDirect | .NET 10.0 | 7.192 ns | 0.30 | 37 | 96 B | - | NA |
| SumEnumerable | .NET 9.0 | 30.695 ns | 1.00 | 103 | 531 B | 40 B | 1.00 |
| SumEnumerable | .NET 10.0 | 8.672 ns | 0.28 | 50 | 324 B | - | 0.00 |
ConcurrentDictionary的比较结果
csharp
// dotnet run -c Release -f net9.0 --filter "*" --runtimes net9.0 net10.0
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Collections.Concurrent;
BenchmarkSwitcher.FromAssembly(typeof(Tests).Assembly).Run(args);
[MemoryDiagnoser(displayGenColumns: false)]
[HideColumns("Job", "Error", "StdDev", "Median", "RatioSD")]
public partial class Tests
{
private ConcurrentDictionary<int, int> _ints = new(Enumerable.Range(0, 1000).ToDictionary(i => i, i => i));
[Benchmark]
public int EnumerateInts()
{
int sum = 0;
foreach (var kvp in _ints) sum += kvp.Value;
return sum;
}
}
| Method | Runtime | Mean | Ratio | Allocated | Alloc Ratio |
|---|---|---|---|---|---|
| EnumerateInts | .NET 9.0 | 4,232.8 ns | 1.00 | 56 B | 1.00 |
| EnumerateInts | .NET 10.0 | 664.2 ns | 0.16 | - | 0.00 |
九、最后
.NET 10的优化成果标志着一个转折点------性能优化不再是在现有架构上打补丁,而是重新思考整个执行模型。它证明了一个重要观点:最好的性能优化是让代码不做不必要的工作。这种思维转变的影响是深远的。它意味着未来的运行时优化将更加注重理解应用程序的语义意图、预测而非响应性能问题、在系统层面而不仅仅是组件层面进行优化、让高性能成为默认特性而非可选配置。.NET 10不仅带来了性能数字的提升,更重要的是展示了一种更加智能、更加系统的优化方法论。这对于整个软件开发行业的进化方向具有重要的启示意义。
如果你在阅读过程中有任何疑问,或者在实际操作中遇到了困难,欢迎随时与我们交流。我们非常期待听到你的反馈和建议,以便我们能够进一步完善内容,帮助更多开发者。请继续关注我们的公众号"萤火初芒",我们将持续分享更多有趣且实用的技术内容,与大家一起学习交流,共同进步。
原文参考链接:https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/