2017-01-11 24 views
0

私は突然linqの性能について不思議になり、何らかのテストを実行しました。linqがCで遅いのはなぜですか

以下は私のテストコードです。結果はかなり驚くべきものでした。

どのようにlinqが動作し、なぜTryOutよりも遅いのですか?

Public class TestObject 
{ 
.... 
.... 
//this class contain many members 
bool deleted; 
.... 
} 

class Program 
{ 
    public static ConcurrentDictionary<string, TestObject> testDictionary = new ConcurrentDictionary<string, TestObject>(); 

static void Main(string[] args) 
{ 
//testDictionary is initialized in ohter code and is likely to have 10000 elements. 
    RandomTest(0); 
    RandomTest(1); 
    Console.ReadKey(); 
} 

static void RandomTest(int k) 
{ 
    int count = 10000; 
    List<string> randomId = new List<string>(); 
    Random rnd = new Random(); 
    for (int i = 0; i < count; i++) 
    { 
     int randomNumber = rnd.Next(0, testDictionary.Count()); 
     randomId.Add(testDictionary.ElementAt(randomNumber).key); 
    } 
    Stopwatch sw = new Stopwatch(); 
    sw.Start(); 

    if (k == 0) 
    { 

     for (int i = 0; i < count; i++) 
     { 
      var res = checkid(randomId[i]); 
     } 
    } 
    else if (k == 1) 
    { 
     for (int i = 0; i < count; i++) 
     { 
      var res = checkid2(randomId[i]); 
     } 
    } 
    sw.Stop(); 
    Console.WriteLine("Elapsed time : " + sw.Elapsed); 
} 
static bool checkid(string id) 
{ 
    TestObject t; 
    return !testDictionary.TryGetValue(id, out t) ? 
      false : t.deleted ? 
      false : true; 
} 
static bool checkid2(string id) 
{ 
    return testDictionary.Any(t => t.key == id && !t.Value.deleted)? true : false; 
} 

私はこれらの2つの方法10000回と結果がcheckid方法について

以下のように示し、それは主に以下の00取った走っ:00:00.002を。

checkid2メソッドの場合は、主に00:00:02.2と00:00:02.4の間でした。

これは大きな違いです。

これは、キーがIdと等しくなくても削除された変数をチェックするので、checkidメソッドは対応するキーを見つけたときにのみ削除された変数をチェックするためですか?

+2

linqは辞書のハッシュテーブルを使用していないためです。基本的にはo(1)ではなくo(n) –

答えて

7

Dictionary.TryGetValueは、要素を見つけるためにハッシュを使用しているため、O(1)の操作です。

Dictionary.Anyは、条件に一致するものを見つけるためにコレクションを反復処理します。それはO(n)です。

一般的に、LINQはfor/foreachを使用して手作りのループよりも少し遅くなりますが、ほとんどの場合パフォーマンスの違いは関係ありません。あなたが経験していることは、LINQが遅いのではなく、Dictionary<T>.TryGetValueです。内部構造がキーベースの検索に最適化されているためです。あなたがそれを変更した場合はList<T>であり、forというループを書くと線形に同じ検索を行います(LINQはカバーの下にあります)。違いがはるかに小さくなるのを見るでしょう。

0

@MarcinJuraszekが正解しました。しかし、私は答えを完了するためにいくつかの関連するLINQ情報を追加したいと思います。はい反復対ハッシングの動作との主な違いは明らかですが、より多くのあなたがLINQについて知っておく必要があります.NET

ではありcheckidためILは、次のようになります。

IL_0000: ldsfld class [mscorlib]System.Collections.Concurrent.ConcurrentDictionary`2<string, class ConsoleApp5.TestObject> ConsoleApp5.Program::testDictionary 
IL_0005: ldarg.0 
IL_0006: ldloca.s t 
IL_0008: callvirt instance bool class [mscorlib]System.Collections.Concurrent.ConcurrentDictionary`2<string, class ConsoleApp5.TestObject>::TryGetValue(!0, !1&) 
IL_000d: brfalse.s IL_001b 

IL_000f: ldloc.0 
IL_0010: ldfld bool ConsoleApp5.TestObject::deleted 
IL_0015: brtrue.s IL_0019 

IL_0017: ldc.i4.1 
IL_0018: ret 

IL_0019: ldc.i4.0 
IL_001a: ret 

IL_001b: ldc.i4.0 
IL_001c: ret 

それは正確に何を行うのですあなたはdo(あなたがコードで書いたもの)だと思います。

IL_0000: newobj instance void ConsoleApp5.Program/'<>c__DisplayClass4_0'::.ctor() 
IL_0005: stloc.0 
IL_0006: ldloc.0 
IL_0007: ldarg.0 
IL_0008: stfld string ConsoleApp5.Program/'<>c__DisplayClass4_0'::id 
IL_000d: ldsfld class [mscorlib]System.Collections.Concurrent.ConcurrentDictionary`2<string, class ConsoleApp5.TestObject> ConsoleApp5.Program::testDictionary 
IL_0012: ldloc.0 
IL_0013: ldftn instance bool ConsoleApp5.Program/'<>c__DisplayClass4_0'::'<checkid2>b__0'(valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string, class ConsoleApp5.TestObject>) 
IL_0019: newobj instance void class [mscorlib]System.Func`2<valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string, class ConsoleApp5.TestObject>, bool>::.ctor(object, native int) 
IL_001e: call bool [System.Core]System.Linq.Enumerable::Any<valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string, class ConsoleApp5.TestObject>>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [mscorlib]System.Func`2<!!0, bool>) 
IL_0023: brtrue.s IL_0027 

IL_0025: ldc.i4.0 
IL_0026: ret 

IL_0027: ldc.i4.1 
IL_0028: ret 

をと(<>c__DisplayClass4_0.<checkid2>b__0下)IDロジックはここにある "本当の" チェック:

しかしcheckid2はこれを行う

IL_0000: ldarga.s t 
IL_0002: call instance !0 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string, class ConsoleApp5.TestObject>::get_Key() 
IL_0007: ldarg.0 
IL_0008: ldfld string ConsoleApp5.Program/'<>c__DisplayClass4_0'::id 
IL_000d: call bool [mscorlib]System.String::op_Equality(string, string) 
IL_0012: brfalse.s IL_0024 

IL_0014: ldarga.s t 
IL_0016: call instance !1 valuetype [mscorlib]System.Collections.Generic.KeyValuePair`2<string, class ConsoleApp5.TestObject>::get_Value() 
IL_001b: ldfld bool ConsoleApp5.TestObject::deleted 
IL_0020: ldc.i4.0 
IL_0021: ceq 
IL_0023: ret 

IL_0024: ldc.i4.0 
IL_0025: ret 

タイプ<>c__DisplayClass4.0生成された新しいコンパイラを作成するこのコードは、保存idをクラスメンバーとして、<>c__DisplayClass4_0'::'<checkid2>b__0に代理人を作成し、KeyValuePairを取得してboolを返し、Anyを呼び出してこのデリゲートを使用します。

これに加えて、Marcinが書いたように、AnyがO(n)でDictionaryがO(1)であるという事実は、あなたの答えを得ました。

関連する問題