【问题标题】:Why could COM interop layer be 40 times slower when client is compiled in VS 2010 vs VS 2005?为什么在 VS 2010 和 VS 2005 中编译客户端时 COM 互操作层会慢 40 倍?
【发布时间】:2012-09-27 02:41:29
【问题描述】:

我的团队使用大型模拟应用程序的 COM API。大多数模拟文件运行到数百兆字节,并且在打开时似乎已完全加载到内存中。

我们执行的主要任务是遍历文件对象模型中的所有元素,然后对每个元素执行“某些操作”。

我们最近在 VS 2010 中将代码库从 .NET 2 迁移到了 .NET 4,并且发现迭代速度下降了大约 40 倍(从大约 10 秒到大约 8 分钟)。我们已将其简化为尽可能小的代码示例(10 行左右);在 VS 2005 中编译,运行,然后在 VS 2010 中打开项目并编译,框架为 2(我们使用的是制造商提供的 COM 互操作程序集)。

2005 年测试应用在 10 秒内完成,而 2010 年则需要 8 分钟。

这可能是什么原因造成的?

更新

代码相当于:

var server = new Server();
var elements = server.Elements;
var elementCount = elements.Count;

for(int i = 0; i < elementsCount; ++i)
{
    var element = elements[i];
}

此代码在 VS 2010 中运行所需的时间是 VS 2005 的 40 倍。

更新 2

我解释说,在一种情况下操作比另一种情况要慢得多的唯一原因是数据在不同版本中通过 COM 传输的方式不同。

我们记录了这两种情况的绑定日志,这就是我们发现的;在快速版本中,找到 CustomMarshalers 的原生镜像(这些是 FUSLOGVW 捕获的绑定日志)

mscorlib

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.HTM

快速

LOG: Start binding of native image mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
LOG: Start validating native image mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
WRN: Native image does not satisfy request. Looking for next native image.
WRN: No matching native image found.

LOG: Start binding of native image mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
LOG: Start validating native image mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
LOG: Bind to native image succeeded.

CustomMarshalers

CustomMarshalers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

快速

LOG: Start binding of native image CustomMarshalers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
LOG: Start validating native image CustomMarshalers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
WRN: Native image does not satisfy request. Looking for next native image.
WRN: No matching native image found.

LOG: Start binding of native image CustomMarshalers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
LOG: Start validating native image CustomMarshalers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
LOG: Start validating all the dependencies.
LOG: [Level 1]Start validating native image dependency mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089.
LOG: Dependency evaluation succeeded.
LOG: [Level 1]Start validating IL dependency Microsoft.VisualC, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a.
LOG: Dependency evaluation succeeded.
LOG: Validation of dependencies succeeded.
LOG: Start loading all the dependencies into load context.
LOG: Loading of dependencies succeeded.
LOG: Bind to native image succeeded.
Native image has correct version information.
Attempting to use native image C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\CustomMarshalers\3e6deccf191ab943d3a0812a38ab5c97\CustomMarshalers.ni.dll.
Native image successfully used.

因此,当原生图像被使用时,我们的性能似乎得到了很大的提升。

为什么这种绑定在一种情况下会失败而在另一种情况下会成功,我们如何强制应用程序不使用本机图像?

更新 3

怪事还在继续。如果我在 VS 2010 中使用 R# 测试运行器或内置的 Visual Studio 测试运行器在测试方法中运行此代码,那么它运行速度很快。

我已经尝试将这段代码包装在一个程序集中,然后动态加载它,这没有任何区别。

【问题讨论】:

  • 我有点困惑从 VS2005 到 VS2010 的变化。是 COM 服务器(本机 C++ 代码)还是 COM 客户端(C# 代码)或两者兼而有之?您是否尝试过隔离哪个部分变慢了?你有没有用VS2005编译的代码,但是VS2010安装的.NET版本(是的,.NET 2.x的行为会在安装更高版本时更新)。
  • 据我所知,这里的本机映像并不像非托管那样表示本机,它意味着它是一个已针对当前架构进行预编译和优化的托管程序集(在这种情况下 x86) 而不是被 JIT 编译:msdn.microsoft.com/en-us/library/vstudio/6t9t5wcf(v=vs.90).aspx。真正奇怪的一点显然是相反的情况:本机库的性能要差得多。
  • ngen 没有太多 XP,但值得再次尝试对有问题的程序集进行 ngenning 吗?也许本机图像存在某种问题。如果做不到这一点,是否值得只删除本机版本?
  • 它是否是使用 Profile 和 Debug 标志生成的,因此会导致在本机程序集中出现额外的调试垃圾?
  • @TheMouthofaCow:我不是在问你的“原生图像”ngen-ed 程序集。我说的是实际的 COM 服务器。是否使用 VS2010 重新编译过,还是相同的二进制文件?

标签: c# visual-studio-2010 visual-studio com visual-studio-2005


【解决方案1】:

这有点远。很高兴我能帮上忙。

在对任何 COM 对象进行大量不同调用时,匹配 MTA 与 STA(线程模型)非常重要。方法顶部的[STAThread] 指令是确保该方法中每个调用的线程模型的一种方法。

看起来Thread.SetApartmentState(ApartmentState.STA) 将适用于整个线程,但显然不适用于线程池线程。

【讨论】:

  • 干杯,非常感谢。显然我不能再奖励两个小时的赏金,但一旦我可以,它将是你的 :)
  • P.S. - 如果您的后台线程调用定义为 [STAThread] 的方法(并且循环在 STA 方法内),那么您仍然应该看到性能提升。只进入公寓一次,而不是之前的 n 次。
  • 我认为这也不太正确。如果您的后台线程不是首先创建对象的线程,那么无论它是否是 STA 本身,它都会通过代理。我不认为你的回答是准确的。
  • 注意:经过进一步测试:[STAThread] 仅适用于 main() 等某些函数。例如,它不适用于手动创建的线程,只有 Thread.SetApartmentState() 在这种情况下有效。
  • 但为了清楚起见,SetApartmentState() 是不够的。该对象还必须由该线程“拥有” - 它必须首先创建它。简单地让多个线程都标记为 STA 并尝试调用在其他线程上创建的 STA 对象(如在线程池模型中)将会很慢。
【解决方案2】:

当您说“……即使在 STA 应用程序中,线程也是……”时,这实际上是不正确的。线程可以选择在访问任何 COM 对象之前设置其单元状态,但在 .NET 中,如果您什么都不做,这些线程将隐含地成为 MTA。

线程池是 MTA。如果您考虑一下,就必须如此,因为如果它充满了 STA 线程,那将是一个糟糕的线程池,因为任何时候线程尝试访问在池中的其他线程之一上创建的对象时都需要编组。

Thread.SetApartmentState 根据定义仅适用于每个线程。它永远不会影响任何其他线程(正如您所发现的)。对象属于一个单元,一个线程可能属于一个单线程模型。如果线程试图访问模型不匹配的对象,则需要对其进行编组。

如果您的 COM 服务器被标记为“两者”,那么您可以在没有来自 STA 或 MTA 线程的代理的情况下使用它。如果是这种情况,那么您很幸运,您应该在 MTA 线程上创建它(或让线程池线程这样做)。

如果您在 STA 线程上创建它,即使(特别是如果)所有其他线程都是 STA,它们都将通过代理,除非您碰巧从最初创建它的线程调用该对象。

如果您的 COM 服务器是单线程的,那么您需要确保您不仅从 STA 线程调用它,而且从首先创建它的 STA 线程调用它,否则您将通过代理进行编组。

【讨论】:

  • 我相信如果这一切都在一个进程中(COM 服务器和 COM 客户端),那么所有 STA 真的只有一个线程。如果您使用任何 STA 单元一次多次调用 STA 对象,则应将“锁定”保持在该单个线程上,直到其他人打断您,或者直到您完成并因此获得一些性能提升。无论出于何种原因,MTA 线程似乎都会在每个方法返回时释放此锁。 (因此在某些情况下性能会受到很大影响)
  • 我不太关注你。 STA 和线程之间存在 1:1 的关系。一个线程创建一个 STA。该线程创建的对象将仅存在于该单元中,并保证每个对象都被该线程调用。如果一个线程调用 CoInitializeEx() 它会创建一个不同于第一个 STA 的 STA,对吗?线程必须运行消息泵以通过代理服务来自其他线程的消息请求。通常,您的主线程将有一个消息泵,其他可能没有。基本上,如果您在 任何 其他线程(无论是 MTA 还是 STA)上调用 COM 对象,您将通过代理到达第一个。
  • 我想说的是,如果其他线程使用 STA 线程模型,我希望其他线程也能从性能中受益,特别是如果它是创建对象的同一线程,即使它不是创建该类型对象的第一个线程。我很确定我过去见过这个,但我必须写一个测试才能确定。
  • 我看不到一个基于 STA 的 COM 对象是如何被多个线程直接(合法地)调用的,此外还有创建者线程。 MTA->STA 和 STA->STA 之间可能存在优化,但无论哪种方式,它都会通过代理。
  • 这对于单线程 COM 对象来说似乎有些正确。但是,当使用 Thread.SetApartmentState() 时,我看到单元线程 COM 对象在子线程中获得了性能优势(但不是使用 [STAThread] 指令,这似乎没有效果)
猜你喜欢
  • 2023-03-10
  • 2011-08-21
  • 2010-11-15
  • 2011-06-11
  • 2012-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多