Monday, 26 August 2013

Why is Entity Framework significantly slower when running in a different AppDomain?

Why is Entity Framework significantly slower when running in a different
AppDomain?

We have a Windows service that loads a bunch of plugins (assemblies) in to
their own AppDomain. Each plugin is aligned to a "service boundary" in the
SOA sense, and so is responsible for accessing its own database. We have
noticed that EF is 3 to 5 times slower when in a separate AppDomain.
I know that the first time EF creates a DbContext and hits the database,
it has to do some setup work which has to be repeated per AppDomain (i.e.
not cached across AppDomains). Considering that the EF code is entirely
self-contained to the plugin (and hence self-contained to the AppDomain),
I would have expected the timings to be comparable to the timings from the
parent AppDomain. Why are they different?
Have tried targeting both .NET 4/EF 4.4 and .NET 4.5/EF 5.
Sample code
EF.csproj
Program.cs
class Program
{
static void Main(string[] args)
{
var watch = Stopwatch.StartNew();
var context = new Plugin.MyContext();
watch.Stop();
Console.WriteLine("outside plugin - new MyContext() : " +
watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine("outside plugin - FirstOrDefault(): " +
watch.ElapsedMilliseconds);
var pluginDll =
Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory +
@"..\..\..\EF.Plugin\bin\Debug\EF.Plugin.dll");
var domain = AppDomain.CreateDomain("other");
var plugin = (IPlugin)
domain.CreateInstanceFromAndUnwrap(pluginDll,
"EF.Plugin.SamplePlugin");
plugin.FirstPost();
Console.ReadLine();
}
}
EF.Interfaces.csproj
IPlugin.cs
public interface IPlugin
{
void FirstPost();
}
EF.Plugin.csproj
MyContext.cs
public class MyContext : DbContext
{
public IDbSet<Post> Posts { get; set; }
}
Post.cs
public class Post
{
public int Id { get; set; }
}
SamplePlugin.cs
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void FirstPost()
{
var watch = Stopwatch.StartNew();
var context = new MyContext();
watch.Stop();
Console.WriteLine(" inside plugin - new MyContext() : " +
watch.ElapsedMilliseconds);
watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine(" inside plugin - FirstOrDefault(): " +
watch.ElapsedMilliseconds);
}
}
Sample timings
Note: This is querying against an empty database table - 0 rows.
Run 1
outside plugin - new MyContext() : 55
outside plugin - FirstOrDefault(): 783
inside plugin - new MyContext() : 352
inside plugin - FirstOrDefault(): 2675
Run 2
outside plugin - new MyContext() : 53
outside plugin - FirstOrDefault(): 798
inside plugin - new MyContext() : 355
inside plugin - FirstOrDefault(): 2687
Run 3
outside plugin - new MyContext() : 45
outside plugin - FirstOrDefault(): 778
inside plugin - new MyContext() : 355
inside plugin - FirstOrDefault(): 2683
AppDomain research
After some further research in to the cost of AppDomains, there seems to
be a suggestion that subsequent AppDomains have to re-JIT system DLLs and
so there is an inherent start-up cost in creating an AppDomain. Is that
what is happening here? I would have expected that the JIT-ing would have
been on AppDomain creation, but perhaps it is EF JIT-ing when it is
called?
Reference for re-JIT:
http://msdn.microsoft.com/en-us/magazine/cc163655.aspx#S8
Timings sounds similar, but not sure if related: First WCF connection made
in new AppDomain is very slow

No comments:

Post a Comment