Find () 대 FirstOrDefault ()의 성능
단일 문자열 속성을 갖는 간단한 참조 유형의 큰 시퀀스 내에서 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);
}
이것은 List.Find ()에 열거 자 오버 헤드가 없기 때문입니까? 아니면 이것과 다른 것이 있습니까?
Find()
.Net 팀이 향후 폐기로 표시하지 않기를 바라 면서 거의 두 배 더 빠르게 실행됩니다 .
내가 프로그램을 디 컴파일, 그래서 나는 당신의 결과를 모방 할 수 있었다 사이의 차이가 Find
하고 FirstOrDefault
.
첫 번째는 디 컴파일 된 프로그램입니다. 컴파일을 위해 데이터 개체를 익명 데이터 항목으로 만들었습니다.
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.
So, what is find doing? This is the decompiled Find
method
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
[__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 sugar on using the enumerable pattern. Look at this image
.
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.
Other than that they are basically the same (testing the passed in predicate to see if an item is what you want)
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.
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
)
편집 : 배열 루프를 추가하면 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);
이것은 Find()
방법 보다 5.5 % 더 느리게 실행됩니다 .
따라서 결론 : 배열 요소를 foreach
반복 하는 것이 반복 오버 헤드를 처리하는 것보다 빠릅니다 . (그러나 둘 다 장단점이 있으므로 논리적으로 코드에 맞는 것을 선택하십시오. 또한 속도의 작은 차이로 인해 문제 가 발생하는 경우는 거의 없으므로 유지 관리 / 가독성에 적합한 것을 사용하십시오)
참고 URL : https://stackoverflow.com/questions/14032709/performance-of-find-vs-firstordefault
'programing' 카테고리의 다른 글
루비가없는 Sass 또는 Compass? (0) | 2020.08.22 |
---|---|
iOS 6의 완료 블록에 대한 dispatch_get_current_queue ()의 대안은 무엇입니까? (0) | 2020.08.22 |
Objective-C에서 사용하지 않는 메서드와 #import를 감지하는 방법 (0) | 2020.08.22 |
Java에서 컨텍스트는 정확히 무엇입니까? (0) | 2020.08.22 |
@Component로 클래스에 주석을 달 때 이것이 Spring Bean과 Singleton임을 의미합니까? (0) | 2020.08.22 |