使用Linq注意事项
前言
前一段时间在.Net(Gtihub)社区中看有人说Linq的Count比Where后Count慢两倍,最近这两年在项目中使用Linq的次数越来越多了,毕竟可以减少代码的行数,说Linq是生产力不为过,原先也看到Linq方法的源码(忘记是那个源码了,内部是foreach迭代器),但没有对Linq下的方法进行性能测试.如果是需要性能的地方,还是尽量用for/foreach自己去处理,也可以使用循环展开的方式去优化.对性能没什么要求,那肯定还是使用Linq.测试代码
namespace CSharpBenchmarks.LinqTest
{
[MemoryDiagnoser]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
public class CountTest
{
private static List<int> Numbers = new(1000);
static CountTest()
{
Random random = new Random(10);
for (int i = 0; i < 1000; i++)
{
Numbers.Add(random.Next(int.MinValue, int.MaxValue));
}
}
[Benchmark]
public int LinqCount()
{
return Numbers.Count(num => num > int.MaxValue / 2);
}
[Benchmark]
public int LinqWhereCount()
{
return Numbers.Where(num => num > int.MaxValue / 2).Count();
}
}
}
使用BenchmarkDotNet测试结果:
发现Count比Where后Count性能对比,有差不多3倍的差距,Count比Where后Count的优点,就是没有在堆上分配内存.
Count/Where源码阅读
先看Count的源码:
public static int Count<TSource>(this IEnumerable<TSource> source)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count;
}
if (source is IIListProvider<TSource> listProv)
{
return listProv.GetCount(onlyIfCheap: false);
}
if (source is ICollection collection)
{
return collection.Count;
}
int count = 0;
using (IEnumerator<TSource> e = source.GetEnumerator())
{
checked
{
while (e.MoveNext())
{
count++;
}
}
}
return count;
}
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (predicate == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
}
int count = 0;
foreach (TSource element in source)
{
checked
{
if (predicate(element))
{
count++;
}
}
}
return count;
}
在看Where源码:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (predicate == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
}
if (source is Iterator<TSource> iterator)
{
return iterator.Where(predicate);
}
if (source is TSource[] array)
{
return array.Length == 0 ?
Empty<TSource>() :
new WhereArrayIterator<TSource>(array, predicate);
}
if (source is List<TSource> list)
{
return new WhereListIterator<TSource>(list, predicate);
}
return new WhereEnumerableIterator<TSource>(source, predicate);
}
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
{
if (source == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
}
if (predicate == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.predicate);
}
return WhereIterator(source, predicate);
}
private static IEnumerable<TSource> WhereIterator<TSource>(IEnumerable<TSource> source, Func<TSource, int, bool> predicate)
{
int index = -1;
foreach (TSource element in source)
{
checked
{
index++;
}
if (predicate(element, index))
{
yield return element;
}
}
}
Count和Where后Count性能差距是在哪里呢?在Where源码中有判断是否为数组/List/Enumerable,分别交给相应的类型去处理,在这三个类型WhereEnumerableIterator/WhereArrayIterator/WhereListIterator都有一个GetCount实现,GetCount是根据索引下标实现的.
public int GetCount(bool onlyIfCheap)
{
if (onlyIfCheap)
{
return -1;
}
int count = 0;
foreach (TSource item in _source)
{
if (_predicate(item))
{
checked
{
count++;
}
}
}
return count;
}
在需要性能,自己循环实现
[Benchmark]
public int ForeachCount()
{
int count = 0;
int val = int.MaxValue / 2; //计算放到循环外
foreach (var num in Numbers)
{
if (num > val)
{
count += 1;
}
}
return count;
}
在进行一次性能测试:
秋风
2022-05-29