在“Where”中使用新对象的 Linq 查询性能

Linq query performance with new object in `Where`

这两者之间有性能差异吗:

var listOfFoo = bar.Where(x => x.Id == new Guid("sth")).toList();

还有这个:

var guid = new Guid("sth");
var listOfFoo = bar.Where(x => x.Id == guid).toList();

?

bar 是某种动态集时可能不会(然后查询将转换为 SQL)。
但是如果是简单的枚举呢?

是的,有一个真正的区别:后者使用对实例化一次的字符串的引用,但前者调用每次加载字符串的 lambda,就像每次都评估条件目标部分的任何循环一样之前分配它的值,什么时候可以完成以及编译器不知道如何优化自己。

调用代码:

// List<Guid> source2 = source.Where((Guid x) => x == new Guid("sth")).ToList();
IL_000e: ldloc.1
IL_000f: ldsfld class [mscorlib]System.Func`2<valuetype [mscorlib]System.Guid, bool> ConsoleApp.Program/'<>c'::'<>9__29_0'

实例方法中的编译器 lambda 未优化:

// return x == new Guid("sth");
IL_0000: ldarg.1
IL_0001: ldstr "sth"
IL_0006: newobj instance void [mscorlib]System.Guid::.ctor(string)
IL_000b: call bool [mscorlib]System.Guid::op_Equality(valuetype [mscorlib]System.Guid, valuetype [mscorlib]System.Guid)
IL_0010: ret

编码器的内联预分配:

// Guid guid = new Guid("sth");
IL_0046: ldstr "sth"
IL_004b: newobj instance void [mscorlib]System.Guid::.ctor(string)
IL_0050: stfld valuetype [mscorlib]System.Guid ConsoleApp.Program/'<>c__DisplayClass29_0'::guid

// source2 = source.Where((Guid x) => x == guid).ToList();
IL_0055: ldloc.1
IL_0056: ldloc.0
IL_0057: ldftn instance bool ConsoleApp.Program/'<>c__DisplayClass29_0'::'<Test>b__1'(valuetype [mscorlib]System.Guid)

现在lambda转换的实例方法是:

// return x == guid;
IL_0000: ldarg.1
IL_0001: ldarg.0
IL_0002: ldfld valuetype [mscorlib]System.Guid ConsoleApp.Program/'<>c__DisplayClass29_0'::guid
IL_0007: call bool [mscorlib]System.Guid::op_Equality(valuetype [mscorlib]System.Guid, valuetype [mscorlib]System.Guid)

因此第一个需要较少的字符串对象实例创建,因此内存和时间更少,这显然只有在大型集合上才能察觉。

基础 IEnumerable 集合是什么以及元素来自何处并不重要。

但是对于一个IQueryable来说,IL代码就像是在写一个查询理解语法,针对这个值进行了优化检查,不管多复杂。

lambda 语法不仅转换为理解查询,就好像我们使用了类似函数式的流畅调用链一样:

var query = listOfFoo.AsQueryable().Where(x => x == new Guid("sth")).ToList();

//  List<Guid> list = (from x in source.AsQueryable()
//      where x == new Guid("sth")
//      select x).ToList();
IL_0007: ldloc.0
IL_0008: call class [System.Core]System.Linq.IQueryable`1<!!0> [System.Core]System.Linq.Queryable::AsQueryable<valuetype [mscorlib]System.Guid>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
// (no C# code)
IL_000d: ldtoken [mscorlib]System.Guid
IL_0012: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0017: ldstr "x"
IL_001c: call class [System.Core]System.Linq.Expressions.ParameterExpression [System.Core]System.Linq.Expressions.Expression::Parameter(class [mscorlib]System.Type, string)
IL_0021: stloc.2
IL_0022: ldloc.2
IL_0023: ldtoken method instance void [mscorlib]System.Guid::.ctor(string)
IL_0028: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_002d: castclass [mscorlib]System.Reflection.ConstructorInfo
IL_0032: ldc.i4.1
IL_0033: newarr [System.Core]System.Linq.Expressions.Expression
IL_0038: dup
IL_0039: ldc.i4.0
IL_003a: ldstr "sth"
IL_003f: ldtoken [mscorlib]System.String
IL_0044: call class [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0049: call class [System.Core]System.Linq.Expressions.ConstantExpression [System.Core]System.Linq.Expressions.Expression::Constant(object, class [mscorlib]System.Type)
IL_004e: stelem.ref
IL_004f: call class [System.Core]System.Linq.Expressions.NewExpression [System.Core]System.Linq.Expressions.Expression::New(class [mscorlib]System.Reflection.ConstructorInfo, class [mscorlib]System.Collections.Generic.IEnumerable`1<class [System.Core]System.Linq.Expressions.Expression>)
IL_0054: ldc.i4.0
IL_0055: ldtoken method bool [mscorlib]System.Guid::op_Equality(valuetype [mscorlib]System.Guid, valuetype [mscorlib]System.Guid)
IL_005a: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype [mscorlib]System.RuntimeMethodHandle)
IL_005f: castclass [mscorlib]System.Reflection.MethodInfo
IL_0064: call class [System.Core]System.Linq.Expressions.BinaryExpression [System.Core]System.Linq.Expressions.Expression::Equal(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.Expression, bool, class [mscorlib]System.Reflection.MethodInfo)
IL_0069: ldc.i4.1
IL_006a: newarr [System.Core]System.Linq.Expressions.ParameterExpression
IL_006f: dup
IL_0070: ldc.i4.0
IL_0071: ldloc.2
IL_0072: stelem.ref
IL_0073: call class [System.Core]System.Linq.Expressions.Expression`1<!!0> [System.Core]System.Linq.Expressions.Expression::Lambda<class [mscorlib]System.Func`2<valuetype [mscorlib]System.Guid, bool>>(class [System.Core]System.Linq.Expressions.Expression, class [System.Core]System.Linq.Expressions.ParameterExpression[])
IL_0078: call class [System.Core]System.Linq.IQueryable`1<!!0> [System.Core]System.Linq.Queryable::Where<valuetype [mscorlib]System.Guid>(class [System.Core]System.Linq.IQueryable`1<!!0>, class [System.Core]System.Linq.Expressions.Expression`1<class [mscorlib]System.Func`2<!!0, bool>>)
IL_007d: call class [mscorlib]System.Collections.Generic.List`1<!!0> [System.Core]System.Linq.Enumerable::ToList<valuetype [mscorlib]System.Guid>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)