C#-听说有人不推荐使用Linq!?

先说结论

linq和直接遍历的性能差异点主要还是迭代方式数组字符串这类的foreach都是专门有优化的,而linq都是通用的泛型调用迭代器,如果刚好遇到这类数据又需要高性能就尽量不用linq,其他类型或性能要求不高的还是linq香。(ps:linq写的不好不能怪linq)

背景

起初是看到有人写的博客说不推荐使用Linq!( https://mp.weixin.qq.com/s/FuF1Wp2eMxbamY5-uMErVQ ),还被多个wx订阅号转载,本能地觉得离谱呀!
what?!
unbelievable!

自测

仔细查看,发现这代码写的有问题呀,然后自己重写了一版

部分代码

    [Benchmark]
    public void SumWithLinq()
    {
        int sum = _row.Sum(c => c == ',' ? 0 : c-'0');
    }

    [Benchmark(Baseline = true)]
    public void SumWithLoop()
    {
        int sum = 0;
        for (int i = 0; i < _row.Length; i++)
        {
            var c= _row[i];
            if (c!= ',')
                sum += c-'0';

        }
    }

测试结果

分析

虽然比那个博主的数据好了很多,但是linq确实比直接使用循环慢得多。查看Sum的内部实现,意料之中是用的迭代器,于是继续添加测试

再测

添加foreach测试代码

    [Benchmark]
    public void SumWithForeach()
    {
        int sum = 0;
        foreach (var c in _row)
        {
            if (c != ',')
                checked
                {
                    sum += int.CreateChecked(c-'0');
                }
        }
    }

测试结果

分析

foreach和for居然没有什么区别,那差距是在哪里呢?突然意识到sum的调用方是IEnumerable,于是又添加了如下测试

再测

添加测试代码

    [Benchmark]
    public void SumWithEnumerable()
    {
        int sum = 0;
        foreach (var c in _row as IEnumerable<char>)
        {
            if (c != ',')
                checked
                {
                    sum += int.CreateChecked(c-'0');
                }
        }
    }

测试结果

分析

直接对string做foreach和将string转为可迭代对象再foreach居然差这么多,string的foreach底层实现可能有优化

查看ILSpy

直接foreach:

调用迭代器:

深入了解foreach

参阅文档:.NET 本质论 - 了解 C# foreach 的内部工作原理和使用 yield 的自定义迭代器
https://learn.microsoft.com/zh-cn/archive/msdn-magazine/2017/april/essential-net-understanding-csharp-foreach-internals-and-custom-iterators-with-yield

附完整代码

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

internal class Program
{
    static void Main(string[] args)
    {
        BenchmarkRunner.Run<LinqTest>();
    }
}

[MemoryDiagnoser, MemoryRandomization]
public class LinqTest
{
    private static readonly string _row = "1,2,3,4,5,6,7,8,9,10";

    [Benchmark]
    public void SumWithLinq()
    {
        int sum = _row.Sum(c => c == ',' ? 0 : c-'0');
    }


    [Benchmark]
    public void SumWithEnumerable()
    {
        int sum = 0;
        foreach (var c in _row as IEnumerable<char>)
        {
            if (c != ',')
                checked
                {
                    sum += int.CreateChecked(c-'0');
                }
        }
    }

    [Benchmark]
    public void SumWithForeach()
    {
        int sum = 0;
        foreach (var c in _row)
        {
            if (c != ',')
                checked
                {
                    sum += int.CreateChecked(c-'0');
                }
        }
    }

    [Benchmark(Baseline = true)]
    public void SumWithLoop()
    {
        int sum = 0;
        for (int i = 0; i < _row.Length; i++)
        {
            var c= _row[i];
            if (c!= ',')
                sum += c-'0';

        }
    }
}
posted @ 2024-07-04 13:59  自笑非  阅读(7)  评论(0编辑  收藏  举报