在.Net 9中Enumerable.ToDictionary性能优化

前言

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

性能基准测试代码

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)
				{

				}
			}
		}
	}
}

基准测试结果:

Enumerable.ToDictionary基准性能测试结果
从测试结果看,.Net 8 和 .Net 9在内存分配上是一样的,耗时上提升了将近30%,生成的汇编代码减少5倍多.下面我们具体看看代码是怎么优化的.
在DotNet 9中ToDictionary代码是如何优化的
还是看一下源码:
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