2012-06-14 8 views
6

Tôi biết sự khác biệt giữa IQueryable và IEnumerable, và tôi biết rằng các bộ sưu tập được Linq To Objects hỗ trợ thông qua giao diện IEnumerable.Tại sao IQueryable hai lần nhanh hơn IEnumerable khi sử dụng LINQ To Objects

Câu hỏi của tôi là các truy vấn được thực hiện nhanh gấp hai lần khi bộ sưu tập được chuyển đổi thành một IQueryable.

Hãy l là một đối tượng điền kiểu Danh sách, sau đó một truy vấn LINQ là thời gian nhanh gấp hai lần nếu danh sách l được chuyển đổi thành một IQueryable qua l.AsQueryable().

Tôi đã viết một bài kiểm tra đơn giản với VS2010SP1 và .NET 4.0 thể hiện này:

private void Test() 
{ 
    const int numTests = 1; 
    const int size = 1000 * 1000; 
    var l = new List<int>(); 
    var resTimesEnumerable = new List<long>(); 
    var resTimesQueryable = new List<long>(); 
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); 

    for (int x=0; x<size; x++) 
    { 
    l.Add(x); 
    } 

    Console.WriteLine("Testdata size: {0} numbers", size); 
    Console.WriteLine("Testdata iterations: {0}", numTests); 

    for (int n = 0; n < numTests; n++) 
    { 
    sw.Restart(); 
    var result = from i in l.AsEnumerable() where (i % 10) == 0 && (i % 3) != 0 select i; 
    result.ToList(); 
    sw.Stop(); 
    resTimesEnumerable.Add(sw.ElapsedMilliseconds); 
    } 
    Console.WriteLine("TestEnumerable"); 
    Console.WriteLine(" Min: {0}", Enumerable.Min(resTimesEnumerable)); 
    Console.WriteLine(" Max: {0}", Enumerable.Max(resTimesEnumerable)); 
    Console.WriteLine(" Avg: {0}", Enumerable.Average(resTimesEnumerable)); 

    for (int n = 0; n < numTests; n++) 
    { 
    sw.Restart(); 
    var result = from i in l.AsQueryable() where (i % 10) == 0 && (i % 3) != 0 select i; 
    result.ToList(); 
    sw.Stop(); 
    resTimesQueryable.Add(sw.ElapsedMilliseconds); 
    } 
    Console.WriteLine("TestQuerable"); 
    Console.WriteLine(" Min: {0}", Enumerable.Min(resTimesQueryable)); 
    Console.WriteLine(" Max: {0}", Enumerable.Max(resTimesQueryable)); 
    Console.WriteLine(" Avg: {0}", Enumerable.Average(resTimesQueryable)); 
} 

Chạy thử nghiệm này (với ý numTests == 1 đến 10) sẽ cho kết quả như sau:

Testdata size: 1000000 numbers 
Testdata iterations: 1 
TestEnumerable 
    Min: 44 
    Max: 44 
    Avg: 44 
TestQuerable 
    Min: 37 
    Max: 37 
    Avg: 37 

Testdata size: 1000000 numbers 
Testdata iterations: 10 
TestEnumerable 
    Min: 22 
    Max: 29 
    Avg: 23,9 
TestQuerable 
    Min: 12 
    Max: 22 
    Avg: 13,9 

Lặp lại phép thử nhưng chuyển đổi thứ tự (tức là đo IQuerable đầu tiên và sau đó là IEnumerable) sẽ cho kết quả differenct!

Testdata size: 1000000 numbers 
Testdata iterations: 1 
TestQuerable 
    Min: 75 
    Max: 75 
    Avg: 75 
TestEnumerable 
    Min: 25 
    Max: 25 
    Avg: 25 

Testdata size: 1000000 numbers 
Testdata iterations: 10 
TestQuerable 
    Min: 12 
    Max: 28 
    Avg: 14 
TestEnumerable 
    Min: 22 
    Max: 26 
    Avg: 23,4 

Dưới đây là những câu hỏi của tôi:

  1. Tôi đang làm gì sai?
  2. Tại sao IEnumerable nhanh hơn nếu thử nghiệm được thực hiện sau khi thử nghiệm IQueryable?
  3. Tại sao là IQueryable nhanh hơn khi không. chạy thử nghiệm được tăng lên?
  4. Có hình phạt liên quan đến việc sử dụng IQueryable thay vì IEnumerable?

Tôi hỏi những câu hỏi này vì tôi đã tự hỏi nên sử dụng cái nào để giao diện Kho lưu trữ của mình. Ngay bây giờ họ truy vấn bộ sưu tập trong bộ nhớ (LINQ to Objects), nhưng trong tương lai, có thể là nguồn dữ liệu SQL. Nếu tôi thiết kế các lớp lưu trữ ngay bây giờ với IQueryable Tôi có thể chuyển đổi dễ dàng về sau sang LINQ to SQL. Tuy nhiên nếu có một hình phạt hiệu suất được invloved sau đó gắn bó với IEnumerable trong khi không có SQL tham gia có vẻ là khôn ngoan hơn.

+0

Bạn không chỉ định nếu bạn đang xây dựng trong chế độ phát hành hoặc gỡ lỗi, và bạn không phải là "mồi" các chức năng đầu tiên để bạn có thể nhìn thấy tiếng ồn jitter. (Tôi nghĩ). Về lâu dài, sự khác biệt của một vài ms trên 10.000.000 lần lặp lại không thực sự có vẻ như là một vấn đề lớn. – asawyer

+0

Tôi đang xây dựng ở chế độ gỡ lỗi. Chuyển sang chế độ phát hành và thêm chạy "khởi tạo" (tức là thực thi và thực hiện từng truy vấn một lần) đã trợ giúp: Bây giờ mã ** IEnumerable ** thực thi nhanh hơn một chút so với mã ** IQueryable ** (11ms so với 12ms). Và đó là chính xác những gì tôi mong đợi. Vì vậy, mã thử nghiệm của tôi đã bị lỗi. Cảm ơn các gợi ý! – rbu

+0

"Tôi có thể không chuyển đổi sau này sang LINQ to SQL" ... Tôi rất muốn nghe cách nó hoạt động cho bạn. –

Trả lời

5

Sử dụng LINQPad để kiểm tra mã IL, đây là những gì tôi nhìn thấy:

Đối với mã này:

var l = Enumerable.Range(0,100); 

var result = from i in l.AsEnumerable() where (i % 10) == 0 && (i % 3) != 0 select i; 

này được tạo ra:

IL_0001: ldc.i4.0  
IL_0002: ldc.i4.s 64 
IL_0004: call  System.Linq.Enumerable.Range 
IL_0009: stloc.0  
IL_000A: ldloc.0  
IL_000B: call  System.Linq.Enumerable.AsEnumerable 
IL_0010: ldsfld  UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 
IL_0015: brtrue.s IL_002A 
IL_0017: ldnull  
IL_0018: ldftn  b__0 
IL_001E: newobj  System.Func<System.Int32,System.Boolean>..ctor 
IL_0023: stsfld  UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 
IL_0028: br.s  IL_002A 
IL_002A: ldsfld  UserQuery.CS$<>9__CachedAnonymousMethodDelegate1 
IL_002F: call  System.Linq.Enumerable.Where 
IL_0034: stloc.1  

b__0: 
IL_0000: ldarg.0  
IL_0001: ldc.i4.s 0A 
IL_0003: rem   
IL_0004: brtrue.s IL_0011 
IL_0006: ldarg.0  
IL_0007: ldc.i4.3  
IL_0008: rem   
IL_0009: ldc.i4.0  
IL_000A: ceq   
IL_000C: ldc.i4.0  
IL_000D: ceq   
IL_000F: br.s  IL_0012 
IL_0011: ldc.i4.0  
IL_0012: stloc.0  
IL_0013: br.s  IL_0015 
IL_0015: ldloc.0  
IL_0016: ret   

Và đối với mã này:

var l = Enumerable.Range(0,100); 

var result = from i in l.AsQueryable() where (i % 10) == 0 && (i % 3) != 0 select i; 

Chúng tôi có được điều này:

IL_0001: ldc.i4.0  
IL_0002: ldc.i4.s 64 
IL_0004: call  System.Linq.Enumerable.Range 
IL_0009: stloc.0  
IL_000A: ldloc.0  
IL_000B: call  System.Linq.Queryable.AsQueryable 
IL_0010: ldtoken  System.Int32 
IL_0015: call  System.Type.GetTypeFromHandle 
IL_001A: ldstr  "i" 
IL_001F: call  System.Linq.Expressions.Expression.Parameter 
IL_0024: stloc.2  
IL_0025: ldloc.2  
IL_0026: ldc.i4.s 0A 
IL_0028: box   System.Int32 
IL_002D: ldtoken  System.Int32 
IL_0032: call  System.Type.GetTypeFromHandle 
IL_0037: call  System.Linq.Expressions.Expression.Constant 
IL_003C: call  System.Linq.Expressions.Expression.Modulo 
IL_0041: ldc.i4.0  
IL_0042: box   System.Int32 
IL_0047: ldtoken  System.Int32 
IL_004C: call  System.Type.GetTypeFromHandle 
IL_0051: call  System.Linq.Expressions.Expression.Constant 
IL_0056: call  System.Linq.Expressions.Expression.Equal 
IL_005B: ldloc.2  
IL_005C: ldc.i4.3  
IL_005D: box   System.Int32 
IL_0062: ldtoken  System.Int32 
IL_0067: call  System.Type.GetTypeFromHandle 
IL_006C: call  System.Linq.Expressions.Expression.Constant 
IL_0071: call  System.Linq.Expressions.Expression.Modulo 
IL_0076: ldc.i4.0  
IL_0077: box   System.Int32 
IL_007C: ldtoken  System.Int32 
IL_0081: call  System.Type.GetTypeFromHandle 
IL_0086: call  System.Linq.Expressions.Expression.Constant 
IL_008B: call  System.Linq.Expressions.Expression.NotEqual 
IL_0090: call  System.Linq.Expressions.Expression.AndAlso 
IL_0095: ldc.i4.1  
IL_0096: newarr  System.Linq.Expressions.ParameterExpression 
IL_009B: stloc.3  
IL_009C: ldloc.3  
IL_009D: ldc.i4.0  
IL_009E: ldloc.2  
IL_009F: stelem.ref 
IL_00A0: ldloc.3  
IL_00A1: call  System.Linq.Expressions.Expression.Lambda 
IL_00A6: call  System.Linq.Queryable.Where 
IL_00AB: stloc.1  

Vì vậy, có thể thấy rằng sự khác biệt là phiên bản AsQuerable đang xây dựng một cây biểu thức, AsEnumerable không.

+1

Vâng, sự khác biệt giữa IEnumerable và IQueriable là sử dụng trước đây các đại biểu trong khi sau đó sử dụng các cây biểu thức. Nhưng điều này không trả lời bất kỳ câu hỏi nào của tôi :-( – rbu