gpt4 book ai didi

c# - 为什么 Entity Framework 在不同的 AppDomain 中运行时速度明显变慢?

转载 作者:IT王子 更新时间:2023-10-29 04:17:10 24 4
gpt4 key购买 nike

我们有一个 Windows 服务,可以将一堆插件(程序集)加载到它们自己的 AppDomain 中。每个插件都与 SOA 意义上的“服务边界”对齐,因此负责访问自己的数据库。我们注意到,在单独的 AppDomain 中时,EF 的速度要慢 3 到 5 倍。

我知道 EF 第一次创建 DbContext 并访问数据库时,它必须执行一些必须针对每个 AppDomain 重复的设置工作(即不跨 AppDomain 缓存)。考虑到 EF 代码完全独立于插件(因此独立于 AppDomain),我希望时间与父 AppDomain 的时间相当。它们为什么不同?

已尝试同时针对 .NET 4/EF 4.4 和 .NET 4.5/EF 5。

示例代码

EF.csproj

程序.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; }
}

示例插件.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);
}
}

采样时间

注意事项:

  • 这是针对空数据库表进行查询 - 0 行。
  • 时间安排有意只考虑第一次通话。与父 AppDomain 相比,子 AppDomain 中的后续调用速度,但仍然相对慢 3 到 5 倍。

运行 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

Update 1

Based on @Yasser's suggestion that there is EF communication across the AppDomains, I tried to isolate this further. I don't believe this to be the case.

I have completely removed any EF reference from EF.csproj. I now have enough rep to post images, so this is the solution structure:

EF.sln

As you can see, only the plugin has a reference to Entity Framework. I have also verified that only the plugin has a bin folder with an EntityFramework.dll.

I have added a helper to verify if the EF assembly has been loaded in the AppDomain. I have also verified (not shown) that after the call to the database, additional EF assemblies (e.g. dynamic proxy) are also loaded.

So, checking if EF has loaded at various points:

  1. In Main before calling the plugin
  2. In Plugin before hitting the database
  3. In Plugin after hitting the database
  4. In Main after calling the plugin

... produces:

Main - IsEFLoaded: FalsePlugin - IsEFLoaded: TruePlugin - new MyContext() : 367Plugin - FirstOrDefault(): 2693Plugin - IsEFLoaded: TrueMain - IsEFLoaded: False

So it seems that the AppDomains are fully isolated (as expected) and the timings are the same inside the plugin.

Updated Sample code

Program.cs

class Program
{
static void Main(string[] args)
{
var dir = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\EF.Plugin\bin\Debug");
var evidence = new Evidence();
var setup = new AppDomainSetup { ApplicationBase = dir };
var domain = AppDomain.CreateDomain("other", evidence, setup);
var pluginDll = Path.Combine(dir, "EF.Plugin.dll");
var plugin = (IPlugin) domain.CreateInstanceFromAndUnwrap(pluginDll, "EF.Plugin.SamplePlugin");

Console.WriteLine("Main - IsEFLoaded: " + Helper.IsEFLoaded());
plugin.FirstPost();
Console.WriteLine("Main - IsEFLoaded: " + Helper.IsEFLoaded());

Console.ReadLine();
}
}

Helper.cs

(是的,我不打算为此添加另一个项目......)

public static class Helper
{
public static bool IsEFLoaded()
{
return AppDomain.CurrentDomain
.GetAssemblies()
.Any(a => a.FullName.StartsWith("EntityFramework"));
}
}

示例插件.cs

public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void FirstPost()
{
Console.WriteLine("Plugin - IsEFLoaded: " + Helper.IsEFLoaded());

var watch = Stopwatch.StartNew();
var context = new MyContext();
watch.Stop();
Console.WriteLine("Plugin - new MyContext() : " + watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine("Plugin - FirstOrDefault(): " + watch.ElapsedMilliseconds);

Console.WriteLine("Plugin - IsEFLoaded: " + Helper.IsEFLoaded());
}
}

更新2

@Yasser:System.Data.Entity 仅访问数据库后加载到插件中。最初只有 EntityFramework.dll 被加载到插件中,但数据库后其他 EF 程序集也被加载:

Loaded assemblies

Zipped solution .该站点仅将文件保留 30 天。欢迎推荐更好的文件共享网站。

此外,我很想知道您是否可以通过在主项目中引用 EF 来验证我的发现,并查看原始样本的计时模式是否可重现。

更新 3

需要明确的是,我感兴趣的是首次调用时间分析,其中包括 EF 启动。在第一次调用时,从父 AppDomain 中的 ~800ms 到子 AppDomain 中的 ~2700ms 是非常明显的。在后续调用中,从 ~1ms 到 ~3ms 几乎不会引起注意。为什么第一次调用(包括 EF 启动)在子 AppDomains 中的成本要高得多?

我更新了示例以仅关注 FirstOrDefault() 调用以减少噪音。在父 AppDomain 中运行和在 3 个子 AppDomain 中运行的一些时间安排:

EF.vshost.exe|0|FirstOrDefault(): 768EF.vshost.exe|1|FirstOrDefault(): 1EF.vshost.exe|2|FirstOrDefault(): 1AppDomain0|0|FirstOrDefault(): 2623AppDomain0|1|FirstOrDefault(): 2AppDomain0|2|FirstOrDefault(): 1AppDomain1|0|FirstOrDefault(): 2669AppDomain1|1|FirstOrDefault(): 2AppDomain1|2|FirstOrDefault(): 1AppDomain2|0|FirstOrDefault(): 2760AppDomain2|1|FirstOrDefault(): 3AppDomain2|2|FirstOrDefault(): 1

Updated Sample Code

    static void Main(string[] args)
{
var mainPlugin = new SamplePlugin();

for (var i = 0; i < 3; i++)
mainPlugin.Do(i);

Console.WriteLine();

for (var i = 0; i < 3; i++)
{
var plugin = CreatePluginForAppDomain("AppDomain" + i);

for (var j = 0; j < 3; j++)
plugin.Do(j);

Console.WriteLine();
}

Console.ReadLine();
}

private static IPlugin CreatePluginForAppDomain(string appDomainName)
{
var dir = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory + @"..\..\..\EF.Plugin\bin\Debug");
var evidence = new Evidence();
var setup = new AppDomainSetup { ApplicationBase = dir };
var domain = AppDomain.CreateDomain(appDomainName, evidence, setup);
var pluginDll = Path.Combine(dir, "EF.Plugin.dll");
return (IPlugin) domain.CreateInstanceFromAndUnwrap(pluginDll, "EF.Plugin.SamplePlugin");
}

public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var context = new MyContext();

var watch = Stopwatch.StartNew();
var posts = context.Posts.FirstOrDefault();
watch.Stop();
Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|FirstOrDefault(): " + watch.ElapsedMilliseconds);
}
}

Zipped solution .该站点仅将文件保留 30 天。欢迎推荐更好的文件共享网站。

最佳答案

这似乎只是子 AppDomain 的成本。 rather ancient post (这可能不再相关)表明除了必须对每个子 AppDomain 进行 JIT 编译之外,可能还有其他考虑因素,例如评估安全政策。

Entity Framework 确实具有相对较高的启动成本,因此效果被放大了,但是对于比较调用 System.Data 的其他部分(例如直接的 SqlDataReader)同样可怕:

EF.vshost.exe|0|SqlDataReader: 67EF.vshost.exe|1|SqlDataReader: 0EF.vshost.exe|2|SqlDataReader: 0AppDomain0|0|SqlDataReader: 313AppDomain0|1|SqlDataReader: 2AppDomain0|2|SqlDataReader: 0AppDomain1|0|SqlDataReader: 290AppDomain1|1|SqlDataReader: 3AppDomain1|2|SqlDataReader: 0AppDomain2|0|SqlDataReader: 316AppDomain2|1|SqlDataReader: 2AppDomain2|2|SqlDataReader: 0
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var watch = Stopwatch.StartNew();
using (var connection = new SqlConnection("Data Source=.\\sqlexpress;Initial Catalog=EF.Plugin.MyContext;Integrated Security=true"))
{
var command = new SqlCommand("SELECT * from Posts;", connection);
connection.Open();
var reader = command.ExecuteReader();
reader.Close();
}
watch.Stop();

Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|SqlDataReader: " + watch.ElapsedMilliseconds);
}
}

即使新建一个不起眼的 DataTable 也会膨胀:

EF.vshost.exe|0|DataTable: 0EF.vshost.exe|1|DataTable: 0EF.vshost.exe|2|DataTable: 0AppDomain0|0|DataTable: 12AppDomain0|1|DataTable: 0AppDomain0|2|DataTable: 0AppDomain1|0|DataTable: 11AppDomain1|1|DataTable: 0AppDomain1|2|DataTable: 0AppDomain2|0|DataTable: 10AppDomain2|1|DataTable: 0AppDomain2|2|DataTable: 0
public class SamplePlugin : MarshalByRefObject, IPlugin
{
public void Do(int i)
{
var watch = Stopwatch.StartNew();
var table = new DataTable("");
watch.Stop();

Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + "|" + i + "|DataTable: " + watch.ElapsedMilliseconds);
}
}

关于c# - 为什么 Entity Framework 在不同的 AppDomain 中运行时速度明显变慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18456491/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com