C# Find()与FirstOrDefault()的性能比较
类似问题:C# Find()与FirstOrDefault()的性能比较,c#,.net,performance,linq,C#,.net,Performance,Linq,类似问题: 在具有单个字符串属性的简单引用类型的大序列中搜索Diana得到了一个有趣的结果 using System; using System.Collections.Generic; using System.Linq; public class Customer{ public string Name {get;set;} } Stopwatch watch = new Stopwatch(); const string diana = "Diana"
在具有单个字符串属性的简单引用类型的大序列中搜索Diana得到了一个有趣的结果
using System;
using System.Collections.Generic;
using System.Linq;
public class Customer{
public string Name {get;set;}
}
Stopwatch watch = new Stopwatch();
const string diana = "Diana";
while (Console.ReadKey().Key != ConsoleKey.Escape)
{
//Armour with 1000k++ customers. Wow, should be a product with a great success! :)
var customers = (from i in Enumerable.Range(0, 1000000)
select new Customer
{
Name = Guid.NewGuid().ToString()
}).ToList();
customers.Insert(999000, new Customer { Name = diana }); // Putting Diana at the end :)
//1. System.Linq.Enumerable.DefaultOrFirst()
watch.Restart();
customers.FirstOrDefault(c => c.Name == diana);
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", watch.ElapsedMilliseconds);
//2. System.Collections.Generic.List<T>.Find()
watch.Restart();
customers.Find(c => c.Name == diana);
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", watch.ElapsedMilliseconds);
}
使用系统;
使用System.Collections.Generic;
使用System.Linq;
公共类客户{
公共字符串名称{get;set;}
}
秒表=新秒表();
const string diana=“diana”;
while(Console.ReadKey().Key!=ConsoleKey.Escape)
{
//Armor拥有1000k++客户。哇,这应该是一款非常成功的产品!:)
var客户=(从Enumerable.Range(0,1000000)中的i开始)
选择新客户
{
Name=Guid.NewGuid().ToString()
}).ToList();
Insert(999000,新客户{Name=diana});//将diana放在末尾:)
//1.System.Linq.Enumerable.DefaultOrFirst()
watch.Restart();
customers.FirstOrDefault(c=>c.Name==diana);
看,停;
WriteLine(“Diana是在{0}ms中使用System.Linq.Enumerable.FirstOrDefault()”,watch.ElapsedMilliseconds找到的);
//2.System.Collections.Generic.List.Find()
watch.Restart();
customers.Find(c=>c.Name==diana);
看,停;
WriteLine(“Diana是在{0}ms中找到的,带有System.Collections.Generic.List.Find()”,watch.ElapsedMilliseconds);
}
这是因为List.Find()中没有枚举器开销,还是因为这个加上其他什么
Find()
运行速度几乎是原来的两倍,希望.Net团队将来不会将其标记为过时。我能够模拟您的结果,所以我对您的程序进行了反编译,Find
和FirstOrDefault
之间存在差异
首先是反编译程序。我将您的数据对象设置为anonmyous数据项,仅用于编译
List<\u003C\u003Ef__AnonymousType0<string>> source = Enumerable.ToList(Enumerable.Select(Enumerable.Range(0, 1000000), i =>
{
var local_0 = new
{
Name = Guid.NewGuid().ToString()
};
return local_0;
}));
source.Insert(999000, new
{
Name = diana
});
stopwatch.Restart();
Enumerable.FirstOrDefault(source, c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Linq.Enumerable.FirstOrDefault().", (object) stopwatch.ElapsedMilliseconds);
stopwatch.Restart();
source.Find(c => c.Name == diana);
stopwatch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T>.Find().", (object) stopwatch.ElapsedMilliseconds);
所以它在一个项目数组上迭代是有意义的,因为列表是数组的包装器
但是,可枚举
类上的FirstOrDefault
使用foreach
迭代项目。这将使用迭代器访问列表并移动到下一步。我认为您看到的是迭代器的开销
[__DynamicallyInvokable]
public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
foreach (TSource source1 in source)
{
if (predicate(source1))
return source1;
}
return default (TSource);
}
[[uuu\u DynamicallyInvokable]
公共静态TSource FirstOrDefault(此IEnumerable源,Func谓词)
{
if(source==null)
抛出错误。ArgumentNull(“源”);
if(谓词==null)
抛出错误。ArgumentNull(“谓词”);
foreach(源中的TSource source1)
{
if(谓词(source1))
返回源1;
}
返回默认值(TSource);
}
Foreach只是使用可枚举模式。看看这张图片
我点击foreach查看它在做什么,你可以看到dotpeek想带我去enumerator/current/next实现,这很有意义
除此之外,它们基本上是相同的(测试传入谓词以查看某个项是否是您想要的)我打赌
FirstOrDefault
是通过IEnumerable
实现运行的,也就是说,它将使用标准的foreach
循环进行检查List.Find()
不是Linq()的一部分,可能使用标准的for
循环,从0
到Count
(或者另一个快速内部机制可能直接在其内部/包装数组上运行)。通过消除枚举的开销(并进行版本检查以确保列表未被修改),Find
方法更快
如果添加第三个测试:
//3. System.Collections.Generic.List<T> foreach
Func<Customer, bool> dianaCheck = c => c.Name == diana;
watch.Restart();
foreach(var c in customers)
{
if (dianaCheck(c))
break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T> foreach.", watch.ElapsedMilliseconds);
此方法的运行速度仅比Find()
方法慢5.5%
所以底线是:通过数组元素循环比处理
foreach
迭代开销更快。(但两者都有各自的优点/缺点,所以只需选择在逻辑上对代码有意义的内容。此外,速度上的微小差异很少会导致问题,所以只需使用对可维护性/可读性有意义的内容即可)在FirstOrDefault
之前尝试计时查找()。那么结果是什么呢?@Oded做到了。完全一样。我还连续运行了两次FirstOrDefault,但仍然是23-24毫秒(在我的iCore5上)。看起来没什么意思。性能是否与列表大小成线性关系(对于其他列表大小,FirstOrDefault是否总是需要两倍的时间,或者使用Linq是否需要固定的10毫秒成本)?在Mono上,性能甚至更高:Diana使用System.Collections.Generic.list.Find()在30毫秒内找到。Diana是在176毫秒内使用System.Linq.Enumerable.FirstOrDefault()找到的。每个项对FirstOrDefault
进行三次间接调用,对Find
进行一次间接调用。与foreach和for进行了很好的比较。我总是喜欢可维护性/可读性,而不是为每一纳秒的性能提升而奋斗的糟糕的家伙:)但是对于真正大的序列(特别是当代码在服务器上运行时),查找性能优化是一项常见的任务,而选择速度快两倍的代码使我决定继续使用List.Find()“对于重型作战,”阿尔曼,我基本上同意。在这种情况下,您需要有1000万个条目才能获得真正可感知的性能影响(几百毫秒)。真的,在这一点上,你们可能应该放弃这个O(n)迭代来进行一些O(1)查找,比如键入名称的字典。@chrisnclar,或者甚至是更好的算法O(对不起):)当他说Mono需要176毫秒时,我对另一条评论更为惊讶。这只适用于最简单的单一属性类。如果一台服务器上运行着1000个并发客户端(我们经常遇到类似的情况),那么即使有10000个真实客户,又会发生什么呢?这就是Linq、lambda、委托、迭代器、枚举、反射和其他使用C#使我们的生活更轻松的习惯用法的成本
//3. System.Collections.Generic.List<T> foreach
Func<Customer, bool> dianaCheck = c => c.Name == diana;
watch.Restart();
foreach(var c in customers)
{
if (dianaCheck(c))
break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with System.Collections.Generic.List<T> foreach.", watch.ElapsedMilliseconds);
//4. System.Collections.Generic.List<T> for loop
var customersArray = customers.ToArray();
watch.Restart();
int customersCount = customersArray.Length;
for (int i = 0; i < customersCount; i++)
{
if (dianaCheck(customers[i]))
break;
}
watch.Stop();
Console.WriteLine("Diana was found in {0} ms with an array for loop.", watch.ElapsedMilliseconds);