在.Net 9中Enumerable.ToDictionary性能优化
前言
在.Net 9中对Enumerable.ToDictionary是有一个性能优化的.具体的issue:代码改动其实并不大.移除了部分代码(40行),新增了15行代码.

性能基准测试代码
using System.Collections.Generic;
using System.Linq;
using BenchmarkDotNet.Attributes;
using Bogus;
namespace CSharpBenchmarks.ArrayTest
{
[MemoryDiagnoser]
[DisassemblyDiagnoser(printSource: true)]
public class ToDictionary
{
private List<People> Peoples;
[Params(16, 128)]
public int Count { get; set; }
[GlobalSetup]
public void Setup()
{
//初始化List数据
Peoples = new Faker<People>("zh_CN")
.RuleFor(x => x.Id, x => x.IndexFaker += 1)
.RuleFor(x => x.Name, y => $"{y.Person.LastName}{y.Person.FirstName}")
.UseSeed(1000)
.Generate(1000);
}
[Benchmark]
public void ToMap()
{
Dictionary<int, People> map = null;
for (int i = 0; i < Count; i++)
{
map = Peoples.ToDictionary(p => p.Id); //调用ToDictionary
if (map != null)
{
}
}
}
}
}
基准测试结果:

从测试结果看,.Net 8 和 .Net 9在内存分配上是一样的,耗时上提升了将近30%,生成的汇编代码减少5倍多.下面我们具体看看代码是怎么优化的.

还是看一下源码:
public static Dictionary<TKey, TSource> ToDictionary<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer) where TKey : notnull
{
if (source is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (keySelector is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.keySelector);
}
if (source.TryGetNonEnumeratedCount(out int capacity))
{
if (capacity == 0)
{
return new Dictionary<TKey, TSource>(comparer);
}
if (source is TSource[] array)
{
//数组也转ReadOlySpan
return SpanToDictionary(array, keySelector, comparer);
}
if (source is List<TSource> list)
{
//将list集合转为ReadOnlySpan
ReadOnlySpan<TSource> span = CollectionsMarshal.AsSpan(list);
return SpanToDictionary(span, keySelector, comparer);
}
}
Dictionary<TKey, TSource> d = new Dictionary<TKey, TSource>(capacity, comparer);
foreach (TSource element in source)
{
d.Add(keySelector(element), element);
}
return d;
}
//*** source改为ReadOnlySpan
private static Dictionary<TKey, TSource> SpanToDictionary<TSource, TKey>(ReadOnlySpan<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey>? comparer) where TKey : notnull
{
Dictionary<TKey, TSource> d = new Dictionary<TKey, TSource>(source.Length, comparer);
foreach (TSource element in source)
{
d.Add(keySelector(element), element);
}
return d;
}
秋风
2024-08-26