programing

Find () 대 FirstOrDefault ()의 성능

nasanasas 2020. 8. 22. 08:59
반응형

Find () 대 FirstOrDefault ()의 성능


비슷한 질문 :
Find () 대 Where (). 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

반응형