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