2017-01-11 2 views
0

Мне неожиданно стало интересно узнать о производительности linq и провело некоторое тестирование.Почему linq slow in 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.

Это огромная разница.

Это потому, что checkid2 метод проверяет удаляемую переменную, даже если ключ не равен Id, в то время как проверка метода проверяет удаляемую переменную только тогда, когда она обнаружила соответствующий ключ?

+2

Поскольку linq не использует хэш-таблицу словаря. Его в основном o (n), а не o (1) –

ответ

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_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 

Это делать именно то, что вы думаете, что это делать (что вы написали в коде).

Но checkid2 сделать это:

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):

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 для использования этого делегата.

Добавьте к этому факт, что Any - это O (n) и словарь - O (1) - как писал Марцин, - и вы получили свой ответ.