.NET10的更新中,性能再一次获得了大幅提升。.NET团队尝试将一些频繁使用的操作,尽可能的优化到极限,那么有哪些内容呢?接下来我们就来看其中一些令人兴奋的更新!

逃逸分析

在.NET10之前了,我们声明在代码中数组都将会默认的分配在堆之上, 这将会有相对较大的开销与内存压力,直到stackallocspan的出现,我们有能力较为简单的直接在栈上申请内存,并且span的出现也让我们代码的性能能够得到巨量的优化。

到了.NET10,我们再次迎来了一个巨大的更新,逃逸分析。当编译器分析出函数中的一些对象如果在返回后不再会使用,那么便会尝试将其分配到栈上而不是堆上(分配的开销相对堆要小非常多),退出方法之后其便会自动释放,这样也可以减少GC的压力。

在.NET10中我们有几个部分已经得到了优化...

数组

[Benchmark]
public int TestSum()
{
    int[] numbers = [1, 2, 3];
    int sum = 0;
    foreach (var item in numbers)
    {
        sum += item;
    }
    return sum;
}

当以上代码在.NET10中调用时,编译器能智能的分析出

 

这个时候,数组将不会再被分配到堆上,而是直接将其分配至栈之上,当函数退出时,其自动就会被释放(比较类似C的行为,但如果返回数组了,则会继续分配到堆上,退出也不会自动释放)

以下例子为同样的原理

[Benchmark]
public void Test()
{
    foreach (var item in new string[] { "ABC", "DEF", "GHI"
})
    {
        Use(item);
    }
}

public void Use(string s) { }

测试结果如下,可以看到在.NET10中我们不需要有任何的内存分配!这是一个重大进步,并且有关于这个优化,也是我们一位国人大佬(@hez2010)的pr相关工作

图片

委托

在.NET10中,委托也获得了同样的优化

[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;
}

在.NET10之前代码中将生成一个闭包的类占用了24字节,以及一个委托占用了64字节

但现在,编译器也能发现委托并不会逃逸出方法,于是便能直接将委托内联到代码中,不在需要申请委托相关的内存(这也会利好一点函数式编程)

于是内存申请就会从88字节变为24字节,巨大的进步!

测试结果

图片

不过我们也能发现,事实上连这个24字节的内存我们也有可能能够省略,也许未来的.NET就能做到这一件事。

去抽象

.NET中接口和虚拟方法是实现抽象能力的重要能力,但其也会造成相应的性能损耗,JIT如何理解这种抽象并取消性能惩罚是一件很重要的任务,而在.NET10中也对此做了许多优化。

数组一直是C#中重要的一员,不过其众多的接口实现令优化一直很麻烦,我们先来看一段测试代码

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;
    }
}

这两个方法谁会更快的?可能会觉得是SumForLoop,毕竟直接数组的索引访问,肯定会快过迭代器,迭代器甚至还需要申请内存,但事实是...

图片

为什么?甚至要慢的好多,但如果我们将ToArray切换为ToList, 这个结果就会更符合我们的预期。

图片

不过幸运的是,.NET10解决了这个问题,当然效率也一并得到了提升,酷

图片

理所当然的,部分与此Linq方法的效率也会因此再攀升一次。

Enumerable

Enumerable在此版本也获得了性能提升,其中也包括获得了一些新的方法

var seq = Enumerable.Sequence(1, 10, 2); 
// 1,3,5,7,9
var infSeq = Enumerable.InfiniteSequence(1, 10); 
// 1,11,21,31,...

序列(Sequence),与无限序列(InfiniteSequence)

我想这两个方法特别是Sequence应该被期待很久了,从前我们需要使用Repeat和Range配合Select等方法使用达成类似的效果,而使用序列就可以轻易的实现构建不同步长的序列

而无限序列也可以减少了我们必须要写迭代器的场景。

Linq的优化

Linq的Contains方法在本版本迎来了一次疯狂的优化,或是说本身变得能更智能的理解我们的意图。

例如当我们使用了排序,但最终只是消费了其中第一个值,事实上我们的意图只是获取了其最大值或是最小值。

Contains亦是如此,我们可能会在Contains之前做出很多操作,但实际上最终可能只是需要简单的判断其中是否存在某个值,.NET10的优化让编译器能更智能的理解这一点



[CPUUsageDiagnoser]
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net90)]
[SimpleJob(RuntimeMoniker.Net10_0)]

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);
}

图片

当然 .NET10还有非常非常多的性能相关的优化。如果希望了解全貌,请一定要阅读Stephen Toub发布的文章

https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-10/

微信公众号: @scixing的炼丹房

Bilibili: @无聊的年

【C#探索】.NET10中疯狂的性能优化_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1eRBjBPE8y/?vd_source=1d76161a221ae4f697e4d44ab784eb80#reply284395197953

ssccinng/InsaneLinqNET10: .NET10性能优化https://github.com/ssccinng/InsaneLinqNET10

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐