2017-01-11 2 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; 
} 

나는이 두 가지 방법을 10000 번 실행하고 checkid 방법

아래, 그것은 대부분 미만 00 걸렸다 같은 결과를 보여줍니다 : 00 : 00.002를.

checkid2 방법의 경우 대부분 00 : 00 : 02.2와 00 : 00 : 02.4 사이였습니다.

이것은 큰 차이가 있습니다.

checkid2 메소드가 키가 Id와 같지 않아도 checkid2 메소드가 삭제 된 변수를 검사하기 때문에 checkID 메소드가 해당 키를 찾았을 때만 삭제 된 변수를 검사하기 때문에 이것이 가능합니까?

+2

linq이 사전의 해시 테이블을 사용하지 않기 때문에. 기본적으로 o (n)보다는 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 정보를 추가하고 싶습니다. 예를 반복하는 대 해싱의 동작과의 주요 차이점은 분명하다,하지만 당신은 checkid를 들어 .NET

에 LINQ에 대해 알아야 할 더 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) 인 사실을 추가하면 답변을 얻을 수 있습니다.

관련 문제