Find() と FirstOrDefault() のパフォーマンス [重複] 質問する

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() に Enumerator のオーバーヘッドがないことが原因でしょうか、それともこれに加えて何か他の原因があるのでしょうか?

Find()ほぼ2倍の速さで走り、。ネットチームは将来的にこれを廃止としてマークすることはありません。

ベストアンサー1

あなたの結果を模倣できたので、プログラムを逆コンパイルしたところ、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);

ここで注目すべき重要な点は、FirstOrDefaultが呼び出されるEnumerableのに対し、Findはソース リスト上のメソッドとして呼び出されることです。

では、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);
}

リストは配列のラッパーなので、これは項目の配列を反復処理する意味があります。

しかし、クラスFirstOrDefaultではEnumerableforeachアイテムを反復処理するために を使用します。これはリストにイテレータを使用して次に移動します。あなたが見ているのはイテレータのオーバーヘッドだと思います。

[__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はただ合成糖列挙パターンの使用について。この画像を見てください

ここに画像の説明を入力してください

foreach をクリックしてそれが何をしているかを確認したところ、dotpeek が enumerator/current/next の実装に移動しようとしていることがわかります。これは理にかなっています。

それ以外は基本的に同じです(渡された述語をテストして、アイテムが必要なものかどうかを確認します)

おすすめ記事