【问题标题】:Why is IQueryable twice as fast than IEnumerable when using Linq To Objects为什么使用 Linq To Objects 时 IQueryable 比 IEnumerable 快两倍
【发布时间】:2012-06-14 12:21:06
【问题描述】:

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

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

lList 类型的填充对象,如果将列表 l 转换为IQueryable 通过 l.AsQueryable()

我用 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 似乎更明智。

【问题讨论】:

  • 您没有指定是在发布模式还是调试模式下构建,并且您没有首先“启动”功能,因此您可能会看到抖动噪音。 (我想)。从长远来看,在 10,000,000 次迭代中相差几毫秒似乎并不是什么大问题。
  • 我在调试模式下构建。切换到发布模式并添加“初始化”运行(即执行和具体化每个查询一次)确实有帮助:现在 IEnumerable 代码的执行速度比 IQueryable 代码稍快(11ms与 12 毫秒)。这正是我所期望的。所以我的测试代码有问题。感谢您的提示!
  • “我以后可以轻松切换到 Linq to SQL”...我很想听听你的效果如何。

标签: performance linq ienumerable linq-to-objects iqueryable


【解决方案1】:

使用 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 没有。

【讨论】:

  • 是的,IEnumerable 和 IQueriable 的区别在于前者使用委托,而后者使用表达式树。但这并不能回答我的任何问题:-(
  • @rbu 确实如此 - 代码编译为树数据结构执行得更快 - 但这并不意味着您的程序会更快 - 取决于编译表达式树需要多长时间。
猜你喜欢
  • 2013-06-15
  • 1970-01-01
  • 2010-11-27
  • 1970-01-01
  • 1970-01-01
  • 2020-04-01
  • 2012-09-27
  • 1970-01-01
  • 2010-10-09
相关资源
最近更新 更多