C# Find() 与 FirstOrDefault() 的性能
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14032709/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Performance of Find() vs. FirstOrDefault()
提问by Arman McHitarian
Similar Question:
Find() vs. Where().FirstOrDefault()
Got an interesting outcome searching for Diana within a large sequence of a simple reference type having a single string property.
在具有单个字符串属性的简单引用类型的大序列中搜索 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);
}
Is this because of no Enumerator overhead in List.Find() or this plus maybe something else?
这是因为 List.Find() 中没有 Enumerator 开销,还是因为这个加上其他原因?
Find()
runs almost as twice as faster, hoping .Netteam will not mark it Obsolete in the future.
Find()
运行速度几乎是原来的两倍,希望.Net团队将来不会将其标记为过时。
采纳答案by devshorts
I was able to mimic your results so I decompiled your program and there is a difference between Find
and FirstOrDefault
.
我能模仿你的结果,所以我反编译的程序和存在的差异Find
和FirstOrDefault
。
First off here is the decompiled program. I made your data object an anonmyous data item just for compilation
首先是反编译的程序。我将您的数据对象设为仅用于编译的匿名数据项
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);
The key thing to notice here is that FirstOrDefault
is called on Enumerable
whereas Find
is called as a method on the source list.
这里要注意的关键是FirstOrDefault
被调用,Enumerable
而Find
作为源列表上的方法被调用。
So, what is find doing? This is the decompiled Find
method
那么,find 是做什么的?这是反编译的Find
方法
private T[] _items;
[__DynamicallyInvokable]
public T Find(Predicate<T> match)
{
if (match == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
for (int index = 0; index < this._size; ++index)
{
if (match(this._items[index]))
return this._items[index];
}
return default (T);
}
So it's iterating over an array of items which makes sense, since a list is a wrapper on an array.
所以它迭代一个有意义的项目数组,因为列表是数组的包装器。
However, FirstOrDefault
, on the Enumerable
class, uses foreach
to iterate the items. This uses an iterator to the list and move next. I think what you are seeing is the overhead of the iterator
但是,FirstOrDefault
在Enumerable
类上,用于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);
}
Foreach is just syntatic sugaron using the enumerable pattern. Look at this image
Foreach 只是使用可枚举模式的语法糖。看看这张图
.
.
I clicked on foreach to see what it's doing and you can see dotpeek wants to take me to the enumerator/current/next implementations which makes sense.
我点击了 foreach 来查看它在做什么,你可以看到 dotpeek 想要带我到 enumerator/current/next 实现,这是有道理的。
Other than that they are basically the same (testing the passed in predicate to see if an item is what you want)
除此之外,它们基本相同(测试传入的谓词以查看某个项目是否是您想要的)
回答by Chris Sinclair
I'm wagering that FirstOrDefault
is running via the IEnumerable
implementation, that is, it will use a standard foreach
loop to do the checking. List<T>.Find()
is not part of Linq (http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx), and is likely using a standard for
loop from 0
to Count
(or another fast internal mechanism probably operating directly on its internal/wrapped array). By getting rid of the overhead of enumerating through (and doing the version checks to ensure that the list hasn't been modified) the Find
method is faster.
我打赌它FirstOrDefault
是通过IEnumerable
实现运行的,也就是说,它将使用标准foreach
循环来进行检查。List<T>.Find()
不是 Linq ( http://msdn.microsoft.com/en-us/library/x0b5b5bc.aspx) 的一部分,并且可能使用标准for
循环从0
to Count
(或其他快速内部机制可能直接在其内部/包装大批)。通过消除枚举的开销(并进行版本检查以确保列表未被修改),该Find
方法更快。
If you add a third test:
如果添加第三个测试:
//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);
That runs about the same speed as the first one (25ms vs 27ms for FirstOrDefault
)
它的运行速度与第一个相同(25ms vs 27ms for FirstOrDefault
)
EDIT: If I add an array loop, it gets pretty close to the Find()
speed, and given @devshorts peek at the source code, I think this is it:
编辑:如果我添加一个数组循环,它会非常接近Find()
速度,并给出@devshorts 查看源代码,我认为就是这样:
//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);
This runs only 5.5% slower than the Find()
method.
这仅比该Find()
方法慢 5.5% 。
So bottom line: looping through array elements is faster than dealing with foreach
iteration overhead. (but both have their pros/cons, so just choose what makes sense for your code logically. Furthermore, only rarely will the small difference in speed evercause an issue, so just use what makes sense for maintainability/readability)
所以底线:遍历数组元素比处理foreach
迭代开销更快。(但两者各有利弊/缺点,所以才选择什么有意义的代码逻辑。而且,很少会在速度上的微小差异日益引起问题,所以只使用什么有意义的可维护性/可读性)