Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does ToString() degrade Entity Framework's performance so dramatically

I have this simple LINQ query, which executes for 8395 ms:

var _context = new SurveyContext(); 
_context.Database.Log = Console.WriteLine;
 (from p in _context.Participants
  join row in _context.ListAnswerSelections
      on new {p.Id, QuestionId = 434} equals
      new {Id = row.RelatedParticipantId, QuestionId = row.RelatedQuestionId}
  select new { V = row.NumericValue.ToString() })
  .ToList()
  .Select(x => new {R = x.V})
  .Count()  //This is just to see one number instead of whole result
  .Dump("Results");

Its IL:

IL_0000:  nop         
IL_0001:  newobj      Survey.Model.SurveyContext..ctor
IL_0006:  stloc.0     // _context
IL_0007:  ldloc.0     // _context
IL_0008:  callvirt    System.Data.Entity.DbContext.get_Database
IL_000D:  ldnull      
IL_000E:  ldftn       System.Console.WriteLine
IL_0014:  newobj      System.Action<System.String>..ctor
IL_0019:  callvirt    System.Data.Entity.Database.set_Log
IL_001E:  nop         
IL_001F:  ldloc.0     // _context
IL_0020:  callvirt    Survey.Model.SurveyContext.get_Participants
IL_0025:  ldloc.0     // _context
IL_0026:  callvirt    Survey.Model.SurveyContext.get_ListAnswerSelections
IL_002B:  ldtoken     Survey.Model.Participant
IL_0030:  call        System.Type.GetTypeFromHandle
IL_0035:  ldstr       "p"
IL_003A:  call        System.Linq.Expressions.Expression.Parameter
IL_003F:  stloc.1     // CS$0$0000
IL_0040:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>..ctor
IL_0045:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_004A:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_004F:  castclass   System.Reflection.ConstructorInfo
IL_0054:  ldc.i4.2    
IL_0055:  newarr      System.Linq.Expressions.Expression
IL_005A:  stloc.2     // CS$0$0001
IL_005B:  ldloc.2     // CS$0$0001
IL_005C:  ldc.i4.0    
IL_005D:  ldloc.1     // CS$0$0000
IL_005E:  ldtoken     Survey.Model.ModelBase.get_Id
IL_0063:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0068:  castclass   System.Reflection.MethodInfo
IL_006D:  call        System.Linq.Expressions.Expression.Property
IL_0072:  stelem.ref  
IL_0073:  ldloc.2     // CS$0$0001
IL_0074:  ldc.i4.1    
IL_0075:  ldc.i4      B2 01 00 00 
IL_007A:  box         System.Int32
IL_007F:  ldtoken     System.Int32
IL_0084:  call        System.Type.GetTypeFromHandle
IL_0089:  call        System.Linq.Expressions.Expression.Constant
IL_008E:  stelem.ref  
IL_008F:  ldloc.2     // CS$0$0001
IL_0090:  ldc.i4.2    
IL_0091:  newarr      System.Reflection.MethodInfo
IL_0096:  stloc.3     // CS$0$0002
IL_0097:  ldloc.3     // CS$0$0002
IL_0098:  ldc.i4.0    
IL_0099:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>.get_Id
IL_009E:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_00A3:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_00A8:  castclass   System.Reflection.MethodInfo
IL_00AD:  stelem.ref  
IL_00AE:  ldloc.3     // CS$0$0002
IL_00AF:  ldc.i4.1    
IL_00B0:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>.get_QuestionId
IL_00B5:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_00BA:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_00BF:  castclass   System.Reflection.MethodInfo
IL_00C4:  stelem.ref  
IL_00C5:  ldloc.3     // CS$0$0002
IL_00C6:  call        System.Linq.Expressions.Expression.New
IL_00CB:  ldc.i4.1    
IL_00CC:  newarr      System.Linq.Expressions.ParameterExpression
IL_00D1:  stloc.s     04 // CS$0$0003
IL_00D3:  ldloc.s     04 // CS$0$0003
IL_00D5:  ldc.i4.0    
IL_00D6:  ldloc.1     // CS$0$0000
IL_00D7:  stelem.ref  
IL_00D8:  ldloc.s     04 // CS$0$0003
IL_00DA:  call        System.Linq.Expressions.Expression.Lambda
IL_00DF:  ldtoken     Survey.Model.ListAnswerSelection
IL_00E4:  call        System.Type.GetTypeFromHandle
IL_00E9:  ldstr       "row"
IL_00EE:  call        System.Linq.Expressions.Expression.Parameter
IL_00F3:  stloc.1     // CS$0$0000
IL_00F4:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>..ctor
IL_00F9:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_00FE:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0103:  castclass   System.Reflection.ConstructorInfo
IL_0108:  ldc.i4.2    
IL_0109:  newarr      System.Linq.Expressions.Expression
IL_010E:  stloc.2     // CS$0$0001
IL_010F:  ldloc.2     // CS$0$0001
IL_0110:  ldc.i4.0    
IL_0111:  ldloc.1     // CS$0$0000
IL_0112:  ldtoken     Survey.Model.ListAnswerSelection.get_RelatedParticipantId
IL_0117:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_011C:  castclass   System.Reflection.MethodInfo
IL_0121:  call        System.Linq.Expressions.Expression.Property
IL_0126:  stelem.ref  
IL_0127:  ldloc.2     // CS$0$0001
IL_0128:  ldc.i4.1    
IL_0129:  ldloc.1     // CS$0$0000
IL_012A:  ldtoken     Survey.Model.ListAnswerSelection.get_RelatedQuestionId
IL_012F:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0134:  castclass   System.Reflection.MethodInfo
IL_0139:  call        System.Linq.Expressions.Expression.Property
IL_013E:  stelem.ref  
IL_013F:  ldloc.2     // CS$0$0001
IL_0140:  ldc.i4.2    
IL_0141:  newarr      System.Reflection.MethodInfo
IL_0146:  stloc.3     // CS$0$0002
IL_0147:  ldloc.3     // CS$0$0002
IL_0148:  ldc.i4.0    
IL_0149:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>.get_Id
IL_014E:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_0153:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0158:  castclass   System.Reflection.MethodInfo
IL_015D:  stelem.ref  
IL_015E:  ldloc.3     // CS$0$0002
IL_015F:  ldc.i4.1    
IL_0160:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>.get_QuestionId
IL_0165:  ldtoken     <>f__AnonymousType0<System.Int32,System.Int32>
IL_016A:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_016F:  castclass   System.Reflection.MethodInfo
IL_0174:  stelem.ref  
IL_0175:  ldloc.3     // CS$0$0002
IL_0176:  call        System.Linq.Expressions.Expression.New
IL_017B:  ldc.i4.1    
IL_017C:  newarr      System.Linq.Expressions.ParameterExpression
IL_0181:  stloc.s     04 // CS$0$0003
IL_0183:  ldloc.s     04 // CS$0$0003
IL_0185:  ldc.i4.0    
IL_0186:  ldloc.1     // CS$0$0000
IL_0187:  stelem.ref  
IL_0188:  ldloc.s     04 // CS$0$0003
IL_018A:  call        System.Linq.Expressions.Expression.Lambda
IL_018F:  ldtoken     Survey.Model.Participant
IL_0194:  call        System.Type.GetTypeFromHandle
IL_0199:  ldstr       "p"
IL_019E:  call        System.Linq.Expressions.Expression.Parameter
IL_01A3:  stloc.1     // CS$0$0000
IL_01A4:  ldtoken     Survey.Model.ListAnswerSelection
IL_01A9:  call        System.Type.GetTypeFromHandle
IL_01AE:  ldstr       "row"
IL_01B3:  call        System.Linq.Expressions.Expression.Parameter
IL_01B8:  stloc.s     05 // CS$0$0004
IL_01BA:  ldtoken     <>f__AnonymousType1<System.String>..ctor
IL_01BF:  ldtoken     <>f__AnonymousType1<System.String>
IL_01C4:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_01C9:  castclass   System.Reflection.ConstructorInfo
IL_01CE:  ldc.i4.1    
IL_01CF:  newarr      System.Linq.Expressions.Expression
IL_01D4:  stloc.2     // CS$0$0001
IL_01D5:  ldloc.2     // CS$0$0001
IL_01D6:  ldc.i4.0    
IL_01D7:  ldloc.s     05 // CS$0$0004
IL_01D9:  ldtoken     Survey.Model.ListAnswerSelection.get_NumericValue
IL_01DE:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_01E3:  castclass   System.Reflection.MethodInfo
IL_01E8:  call        System.Linq.Expressions.Expression.Property
IL_01ED:  ldtoken     System.Int32.ToString
IL_01F2:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_01F7:  castclass   System.Reflection.MethodInfo
IL_01FC:  ldc.i4.0    
IL_01FD:  newarr      System.Linq.Expressions.Expression
IL_0202:  call        System.Linq.Expressions.Expression.Call
IL_0207:  stelem.ref  
IL_0208:  ldloc.2     // CS$0$0001
IL_0209:  ldc.i4.1    
IL_020A:  newarr      System.Reflection.MethodInfo
IL_020F:  stloc.3     // CS$0$0002
IL_0210:  ldloc.3     // CS$0$0002
IL_0211:  ldc.i4.0    
IL_0212:  ldtoken     <>f__AnonymousType1<System.String>.get_V
IL_0217:  ldtoken     <>f__AnonymousType1<System.String>
IL_021C:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0221:  castclass   System.Reflection.MethodInfo
IL_0226:  stelem.ref  
IL_0227:  ldloc.3     // CS$0$0002
IL_0228:  call        System.Linq.Expressions.Expression.New
IL_022D:  ldc.i4.2    
IL_022E:  newarr      System.Linq.Expressions.ParameterExpression
IL_0233:  stloc.s     04 // CS$0$0003
IL_0235:  ldloc.s     04 // CS$0$0003
IL_0237:  ldc.i4.0    
IL_0238:  ldloc.1     // CS$0$0000
IL_0239:  stelem.ref  
IL_023A:  ldloc.s     04 // CS$0$0003
IL_023C:  ldc.i4.1    
IL_023D:  ldloc.s     05 // CS$0$0004
IL_023F:  stelem.ref  
IL_0240:  ldloc.s     04 // CS$0$0003
IL_0242:  call        System.Linq.Expressions.Expression.Lambda
IL_0247:  call        System.Linq.Queryable.Join
IL_024C:  call        System.Linq.Enumerable.ToList
IL_0251:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_0256:  brtrue.s    IL_026B
IL_0258:  ldnull      
IL_0259:  ldftn       UserQuery.<Main>b__1
IL_025F:  newobj      System.Func<<>f__AnonymousType1<System.String>,<>f__AnonymousType2<System.String>>..ctor
IL_0264:  stsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_0269:  br.s        IL_026B
IL_026B:  ldsfld      UserQuery.CS$<>9__CachedAnonymousMethodDelegate2
IL_0270:  call        System.Linq.Enumerable.Select
IL_0275:  call        System.Linq.Enumerable.Count
IL_027A:  ldstr       "Results"
IL_027F:  call        LINQPad.Extensions.Dump
IL_0284:  pop         
IL_0285:  ret         

<Main>b__1:
IL_0000:  ldarg.0     
IL_0001:  callvirt    <>f__AnonymousType1<System.String>.get_V
IL_0006:  newobj      <>f__AnonymousType2<System.String>..ctor
IL_000B:  stloc.0     // CS$1$0000
IL_000C:  br.s        IL_000E
IL_000E:  ldloc.0     // CS$1$0000
IL_000F:  ret         

<>f__AnonymousType1`1.get_V:
IL_0000:  ldarg.0     
IL_0001:  ldfld       16 00 00 0A 
IL_0006:  stloc.0     
IL_0007:  br.s        IL_0009
IL_0009:  ldloc.0     
IL_000A:  ret         

<>f__AnonymousType1`1.ToString:
IL_0000:  newobj      System.Text.StringBuilder..ctor
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  ldstr       "{ V = "
IL_000C:  callvirt    System.Text.StringBuilder.Append
IL_0011:  pop         
IL_0012:  ldloc.0     
IL_0013:  ldarg.0     
IL_0014:  ldfld       16 00 00 0A 
IL_0019:  box         02 00 00 1B 
IL_001E:  callvirt    System.Text.StringBuilder.Append
IL_0023:  pop         
IL_0024:  ldloc.0     
IL_0025:  ldstr       " }"
IL_002A:  callvirt    System.Text.StringBuilder.Append
IL_002F:  pop         
IL_0030:  ldloc.0     
IL_0031:  callvirt    System.Object.ToString
IL_0036:  stloc.1     
IL_0037:  br.s        IL_0039
IL_0039:  ldloc.1     
IL_003A:  ret         

<>f__AnonymousType1`1.Equals:
IL_0000:  ldarg.1     
IL_0001:  isinst      06 00 00 1B 
IL_0006:  stloc.0     
IL_0007:  ldloc.0     
IL_0008:  brfalse.s   IL_0022
IL_000A:  call        10 00 00 0A 
IL_000F:  ldarg.0     
IL_0010:  ldfld       16 00 00 0A 
IL_0015:  ldloc.0     
IL_0016:  ldfld       16 00 00 0A 
IL_001B:  callvirt    11 00 00 0A 
IL_0020:  br.s        IL_0023
IL_0022:  ldc.i4.0    
IL_0023:  nop         
IL_0024:  stloc.1     
IL_0025:  br.s        IL_0027
IL_0027:  ldloc.1     
IL_0028:  ret         

<>f__AnonymousType1`1.GetHashCode:
IL_0000:  ldc.i4      8B 15 0F EC 
IL_0005:  stloc.0     
IL_0006:  ldc.i4      29 55 55 A5 
IL_000B:  ldloc.0     
IL_000C:  mul         
IL_000D:  call        10 00 00 0A 
IL_0012:  ldarg.0     
IL_0013:  ldfld       16 00 00 0A 
IL_0018:  callvirt    14 00 00 0A 
IL_001D:  add         
IL_001E:  stloc.0     
IL_001F:  ldloc.0     
IL_0020:  stloc.1     
IL_0021:  br.s        IL_0023
IL_0023:  ldloc.1     
IL_0024:  ret         

<>f__AnonymousType1`1..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ldarg.0     
IL_0007:  ldarg.1     
IL_0008:  stfld       16 00 00 0A 
IL_000D:  ret         

<>f__AnonymousType2`1.get_R:
IL_0000:  ldarg.0     
IL_0001:  ldfld       17 00 00 0A 
IL_0006:  stloc.0     
IL_0007:  br.s        IL_0009
IL_0009:  ldloc.0     
IL_000A:  ret         

<>f__AnonymousType2`1.ToString:
IL_0000:  newobj      System.Text.StringBuilder..ctor
IL_0005:  stloc.0     
IL_0006:  ldloc.0     
IL_0007:  ldstr       "{ R = "
IL_000C:  callvirt    System.Text.StringBuilder.Append
IL_0011:  pop         
IL_0012:  ldloc.0     
IL_0013:  ldarg.0     
IL_0014:  ldfld       17 00 00 0A 
IL_0019:  box         02 00 00 1B 
IL_001E:  callvirt    System.Text.StringBuilder.Append
IL_0023:  pop         
IL_0024:  ldloc.0     
IL_0025:  ldstr       " }"
IL_002A:  callvirt    System.Text.StringBuilder.Append
IL_002F:  pop         
IL_0030:  ldloc.0     
IL_0031:  callvirt    System.Object.ToString
IL_0036:  stloc.1     
IL_0037:  br.s        IL_0039
IL_0039:  ldloc.1     
IL_003A:  ret         

<>f__AnonymousType2`1.Equals:
IL_0000:  ldarg.1     
IL_0001:  isinst      07 00 00 1B 
IL_0006:  stloc.0     
IL_0007:  ldloc.0     
IL_0008:  brfalse.s   IL_0022
IL_000A:  call        10 00 00 0A 
IL_000F:  ldarg.0     
IL_0010:  ldfld       17 00 00 0A 
IL_0015:  ldloc.0     
IL_0016:  ldfld       17 00 00 0A 
IL_001B:  callvirt    11 00 00 0A 
IL_0020:  br.s        IL_0023
IL_0022:  ldc.i4.0    
IL_0023:  nop         
IL_0024:  stloc.1     
IL_0025:  br.s        IL_0027
IL_0027:  ldloc.1     
IL_0028:  ret         

<>f__AnonymousType2`1.GetHashCode:
IL_0000:  ldc.i4      21 74 32 1C 
IL_0005:  stloc.0     
IL_0006:  ldc.i4      29 55 55 A5 
IL_000B:  ldloc.0     
IL_000C:  mul         
IL_000D:  call        10 00 00 0A 
IL_0012:  ldarg.0     
IL_0013:  ldfld       17 00 00 0A 
IL_0018:  callvirt    14 00 00 0A 
IL_001D:  add         
IL_001E:  stloc.0     
IL_001F:  ldloc.0     
IL_0020:  stloc.1     
IL_0021:  br.s        IL_0023
IL_0023:  ldloc.1     
IL_0024:  ret         

<>f__AnonymousType2`1..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ldarg.0     
IL_0007:  ldarg.1     
IL_0008:  stfld       17 00 00 0A 
IL_000D:  ret         

when it executes, it generates the following SQL query, which takes 661 ms to execute:

SELECT 
    [Extent1].[Id] AS [Id], 
     CAST( [Extent2].[NumericValue] AS nvarchar(max)) AS [C1]
    FROM  [dbo].[Participants] AS [Extent1]
    INNER JOIN [dbo].[ListAnswerSelections] AS [Extent2] ON ([Extent1].[Id] = [Extent2].[ListAnswer_RelatedParticipantId]) AND (434 = [Extent2].[ListAnswer_RelatedQuestionId])


-- Executing at 08-Oct-15 3:26:59 PM +03:00

-- Completed in 661 ms with result: SqlDataReader

What was Entity Framework (version="6.1.0.133") doing all that time?

Little change to the LINQ query, namely removing ToString() call in the select part, makes it execute for 749 ms:

    var _context = new SurveyContext(); 
_context.Database.Log = Console.WriteLine;
 (from p in _context.Participants
  join row in _context.ListAnswerSelections
      on new {p.Id, QuestionId = 434} equals
      new {Id = row.RelatedParticipantId, QuestionId = row.RelatedQuestionId}
  select new { V = row.NumericValue })
  .ToList()
  .Select(x => new { R = x.V.ToString() })
  .Count()
  .Dump("Results");

with the following generated SQL query:

SELECT 
[Extent1].[Id] AS [Id], 
[Extent2].[NumericValue] AS [NumericValue]
FROM  [dbo].[Participants] AS [Extent1]
INNER JOIN [dbo].[ListAnswerSelections] AS [Extent2] ON ([Extent1].[Id] =  [Extent2].[ListAnswer_RelatedParticipantId]) AND (434 = [Extent2].[ListAnswer_RelatedQuestionId])


-- Executing at 08-Oct-15 3:33:33 PM +03:00

-- Completed in 367 ms with result: SqlDataReader

I can see that cast to string has degraded performance of SQL execution, 1.8 times. But the performance of EF degraded 11.2 times. Why is this the case? And if I really need to have ToString() in my select section of the query, how can I avoid this performance penalty?

like image 519
Oleksandr Nechai Avatar asked Oct 08 '15 12:10

Oleksandr Nechai


People also ask

Does Entity Framework cache data?

Entity Framework has the following forms of caching built-in: Object caching – the ObjectStateManager built into an ObjectContext instance keeps track in memory of the objects that have been retrieved using that instance. This is also known as first-level cache.

What is difference between Entity Framework 5 and 6?

EF5 is built into the core of . NET 4.5, whereas EF6 has been shifted out, and is open source. This means that you must add the new EF6 assemblies to all of the relevant projects in the solution, in particular the entry project. This means that you must remove assembly System.

Does AsEnumerable execute the query?

AsEnumerable() would execute the query using LINQ-to-SQL, then use LINQ-to-Objects for the Count , Sum , and Average . Instead, when I do this in LINQPad, I see the same query is run three times. If I replace . AsEnumerable() with .

Is contain slow?

Using Contains in Entity Framework is actually very slow. It's true that it translates into an IN clause in SQL and that the SQL query itself is executed fast. But the problem and the performance bottleneck is in the translation from your LINQ query into SQL.


1 Answers

So I ran the following two expressions through LINQPad on AdventureWorks to test out a theory:

from p in Persons
join row in BusinessEntityAddresses
    on new {p.BusinessEntityID}  equals new {row.BusinessEntityID}
select new { V = row.ModifiedDate }

and

from p in Persons
join row in BusinessEntityAddresses
    on new {p.BusinessEntityID}  equals new {row.BusinessEntityID}
select new { V = row.ModifiedDate.ToString() }

Both had some reflection involved:

IL_017D:  ldtoken     <>f__AnonymousType1<System.String>.get_V
IL_0182:  ldtoken     <>f__AnonymousType1<System.String>
IL_0187:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_018C:  castclass   System.Reflection.MethodInfo

But the version with select new ... .ToString() generated several additional reflection calls:

IL_014E:  call        System.Reflection.FieldInfo.GetFieldFromHandle
IL_0153:  call        System.Linq.Expressions.Expression.Field
IL_0158:  ldtoken     System.Object.ToString
IL_015D:  call        System.Reflection.MethodBase.GetMethodFromHandle
IL_0162:  castclass   System.Reflection.MethodInfo
IL_0167:  ldc.i4.0    
IL_0168:  newarr      System.Linq.Expressions.Expression
IL_016D:  call        System.Linq.Expressions.Expression.Call
IL_0172:  stelem.ref  
IL_0173:  ldloc.1     // CS$0$0001
IL_0174:  ldc.i4.1    
IL_0175:  newarr      System.Reflection.MethodInfo
IL_017A:  stloc.2     // CS$0$0002
IL_017B:  ldloc.2     // CS$0$0002
IL_017C:  ldc.i4.0    

It seems like calling .ToString in the LINQ Query is causing extra (duplicative) reflection overhead for each entry. When you call it afterwards, the compiler knows how to inline the reflection to get to .ToString more efficiently.

It seems like the bottom line is that you should avoid calling methods within LINQ Queries, as additional reflection overhead (GetMethodFromHandle, MethodInfo) gets baked into your query in ways that you wouldn't expect.

like image 118
Dan Field Avatar answered Oct 20 '22 00:10

Dan Field