7

我知道 IQueryable 和 IEnumerable 之间的区别,并且我知道 Linq To Objects 通过 IEnumerable 接口支持集合。

令我困惑的是,当集合转换为 IQueryable 时,查询的执行速度是原来的两倍。

假设lList类型的填充对象,如果通过l.AsQueryable()将列表l转换为IQueryable,则 linq 查询的速度是两倍。

我用 VS2010SP1 和 .NET 4.0 编写了一个简单的测试来证明这一点:

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 ) );
}

运行此测试(使用 will numTests == 1 和 10)会产生以下输出:

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

重复测试但切换顺序(即先测量 IQuerable,然后测量 IEnumerable)会得到不同的结果!

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

以下是我的问题:

  1. 我究竟做错了什么?
  2. 如果在IQueryable测试之后执行测试,为什么IEnumerable更快?
  3. 为什么没有时IQueryable更快。试运行次数增加?
  4. 使用IQueryable而不是IEnumerable会受到惩罚吗?

我问这些问题是因为我想知道将哪一个用于我的存储库接口。现在他们查询内存中的集合(Linq to Objects),但将来这可能是一个 SQL 数据源。如果我现在使用IQueryable设计存储库类,我以后可以轻松地切换到 Linq to SQL。但是,如果存在性能损失,那么在不涉及 SQL 的情况下坚持使用IEnumerable似乎更明智。

4

1 回答 1

6

使用 linqpad 检查 IL 代码,这就是我所看到的:

对于此代码:

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

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

这是生成的:

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         

对于这段代码:

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

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

我们得到这个:

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     

所以看起来不同之处在于AsQuerable版本正在构建表达式树,AsEnumerable而不是。

于 2012-06-14T12:29:33.820 回答