【问题标题】:XmlSerializer startup HUGE performance loss on 64bit systemsXmlSerializer 在 64 位系统上启动巨大的性能损失
【发布时间】:2010-11-09 18:53:09
【问题描述】:

在一个有很多字段的类上调用简单的 XmlSerializer.Deserizlize() 时,我遇到了非常巨大的性能损失。

注意:我在家里写的代码没有Visual Studio,所以可能会有一些错误。

我的可序列化类是扁平的,有数百个字段:

[Serializable]
class Foo
{
    public Foo() { }

    [XmlElement(ElementName = "Field1")]
    public string Field1;

    // [...] 500 Fields defined in the same way

    [XmlElement(ElementName = "Field500")]
    public string Field500;
}

我的应用程序反序列化一个输入字符串(即使很小):

 StringReader sr = new StringReader(@"<Foo><Field1>foo</Field1></Foo>");
 XmlSerializer serializer = new XmlSerializer(typeof(Foo));
 object o = serializer.Deserialize(sr);

在 32 位系统中运行应用程序(或使用 corflags.exe 强制 32 位),代码第一次大约需要 ONE SECOND(临时序列化类生成,以及所有...),然后它接近于 0。

在 64 位系统中运行应用程序,代码第一次耗时 ONE MINUTE,然后接近 0。

在 64 位系统中,对于一个大类,在第一次执行 XmlSerializer 期间,什么可能会使系统挂起这么长时间?

现在我不确定我是否必须责怪临时类生成/删除、xml 名称表初始化、CAS、Windows 搜索、防病毒或圣诞老人......

剧透

这是我的测试,如果您不想被我的(可能的)分析错误所牵制,请不要阅读此内容。

  • 从 Visual Studio 调试器运行代码使代码即使在 64 位系统中也能快速运行
  • 添加(完全未记录的)system.diagnostic 开关“XmlSerialization.Compile”可防止系统删除序列化临时类,从而使代码即使在 64 位系统中也能快速运行
  • 采用运行时创建的临时 FooXmlSerializer 类,包括我项目中的 .cs,并使用它代替 XmlSerializer,即使在 64 位系统中也可以使代码快速运行
  • 使用 sgen.exe 创建相同的 FooXmlSerializer 类,包括我项目中的 .cs,并使用它而不是 XmlSerializer,使代码即使在 64 位系统中也能快速运行
  • 使用 sgen.exe 创建相同的 FooXmlSerializer 类,在我的项目中引用 Foo.XmlSerializers.dll 程序集,并使用它而不是 XmlSerializer,即使在 64 位系统中也使代码运行缓慢(这让我很烦恼很多
  • 只有当反序列化的输入实际上包含大类的字段时才会发生性能损失(这也让我很烦恼

进一步解释最后一点,如果我有课:

[Serializable]
class Bar
{
    public Bar() { }

    [XmlElement(ElementName = "Foo")]
    public Foo Foo; // my class with 500 fields
}

只有在传递一个 Foo 孩子时,反序列化才会很慢。即使我已经执行了反序列化:

 StringReader sr = new StringReader(@"<Bar></Bar>");
 XmlSerializer serializer = new XmlSerializer(typeof(Bar));
 object o = serializer.Deserialize(sr); // FAST

 StringReader sr = new StringReader(@"<Bar><Foo><Field1>foo</Field1></Foo></Bar>");
 XmlSerializer serializer = new XmlSerializer(typeof(Bar));
 object o = serializer.Deserialize(sr); // SLOW

编辑我忘了说我用进程监视器分析了执行,我没有看到我的应用程序或 csc.exe 或任何与框架相关的任务需要很长时间.系统只是做其他事情(或者我遗漏了一些东西),比如防病毒、explorer.exe、Windows 搜索索引(已经尝试关闭它们)

【问题讨论】:

  • sgen 有什么问题?你为什么不想走那条路?
  • 我的 Foo() 类,以及调用 XmlDeserializer 的代码,都是由旧系统动态生成的,无法重构太多。我已经有了一个可接受的解决方案(使用 system.diagnostic 开关设置),但我真的很想知道是什么挂起系统 :)
  • 所以你的系统不是很忙,它只是坐在那里?那种暗示某种超时,可能试图解析 XML 模式或其他东西。不过,我预计 32 位和 64 位环境中的延迟相同。除非该模式以前以某种方式以 32 位模式缓存。重新设计。 . .
  • class Foo 不能反序列化,因为它是内部的而不是公共的(在您的示例中)。在我的系统上,32 位发布版本需要 0.055 秒来反序列化。与您的结果一样,我的 64 位版本需要 12.5 秒!所以那里的减速大约是 227 倍。
  • +1 希望这个问题得到一些专业的关注。有趣的情况。

标签: .net 64-bit xmlserializer


【解决方案1】:

我完全不知道这是否相关,但我遇到了 XSLT 的问题,并通过 Microsoft 找到了 those rather interesting comments 关于 64 位 JITter:

问题的根源与两件事有关:首先,x64 JIT 编译器有一些二次缩放算法。不幸的是,其中之一是调试信息生成器。所以对于非常大的方法,它真的会失控。

[...]

64 位 JIT 中的一些算法具有多项式缩放。我们实际上正在努力将 32 位 JIT 编译器移植到 x64,但是直到 next 运行时的并行版本(如“2.0 & 4.0 并行运行,但 3.0/3.5/3.5SP1 是“就地”版本)。我已将其切换为“建议”,因此我可以将其连接到 JIT 吞吐量工作项以进行制作确保在新移植的 JIT 准备好发货时解决此问题。

同样,这是一个完全不同的问题,但在我看来,64 位 JITter cmets 是通用的。

【讨论】:

  • 你是对的,这是一个不同的问题,但可能与 64 位 JITter 性能问题有关
  • 其次,JIT对此负有一定责任,我不知道为什么但确实如此!
【解决方案2】:

更新

我能够重现这一点,调查表明大部分时间都花在了 JIT 编译器上:

JittingStarted: "Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo", "Read2_Foo", "instance class SerializersTester.Foo"

您无需任何分析器工具即可轻松证明这一点。

  • 通过 sgen 为 x86 和 x64 目标生成 *.XmlSerializers.dll
  • 通过 ngen 生成本机图像。

您会注意到,与 x86 汇编相比,x64 生成会慢得多

确切的原因隐藏在 x64 JIT 内部(顺便说一句,它与 x86 完全不同),不幸的是我没有足够的空闲时间来找到它。

为避免这种性能损失,您可以通过 sgen 生成序列化程序的程序集,在最终用户 PC 上的应用程序设置期间引用它并通过 ngen 编译为本机映像。

【讨论】:

  • 发生在 .NET 2.0、3.5 和 4.0 上。 “生成序列化程序集”只为 Web 服务引用生成序列化类。我手动使用了 sgen.exe,请参阅我的帖子。尚未使用分析器。
  • “x64 JIT-internals 中隐藏的确切原因” - 希望该主题能够吸引 JIT 团队的成员,他们可以对此有所了解。
  • 尼克,我仍然不明白的是为什么从外部程序集引用的 Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderFoo 类很慢,而添加到项目中却很快。将 foo 类及其序列化程序类放在同一个程序集中是否会给 JIT 编译器带来优势?
  • @Filini,如果不深入研究 64 位抖动内部结构,我无法确切回答您,我只能在这里猜测。或许,您应该将此问题发布到connect.microsoft.com。 x64 JIT 编译器团队对其工作原理了解得更多。
  • +1 表示“通过 sgen 生成序列化程序的程序集,引用它并通过 ngen 编译为本机映像”。这是一个很好的临时修复,但仍然希望 JIT 团队成员看到这一点。
【解决方案3】:

为了澄清“XmlSerialization.compile”,这就是它正在发生的事情:

如果我们在 64 位上运行没有 .config 文件的代码会很慢。

如果我们将以下部分添加到应用程序的 .config 文件中

<configuration>
   <system.diagnostics>
     <switches>
        <add name="XmlSerialization.Compilation" value="4"/>
     </switches>
   </system.diagnostics>
</configuration>

结果如下:

  • .cs 文件、DLL 和序列化程序的 PDB 文件留在 temp 文件夹中
  • 序列化程序启动很快,它仍然比 32 位慢,但绝对可以接受(1-2 秒而不是 60 秒)

也许在调试模式下创建 DLL(因为有可用的 PDB 文件)改变 JIT 编译器的行为,使其再次快速...

【讨论】:

    【解决方案4】:

    自 64 位 .NET 发布以来,Microsoft 就知道这一点:

    http://connect.microsoft.com/VisualStudio/feedback/details/508748/memory-consumption-alot-higher-on-x64-for-xslcompiledtransform-transform-then-on-x86

    来自 MSFT: “x64 JIT 编译器有一些二次缩放算法。......自从 2005 年首次发布 64 位框架以来,我们已经多次看到这种情况。” 和

    “这个问题 a) 已知,b) 解决起来并不容易。这是 64 位 JIT 的设计问题。我们正处于替换 64 位 JIT 实现的早期阶段,因此它将 最终得到地址,但不幸的是,在 CLR 4.0 时间范围内没有。”

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-15
      • 2013-11-04
      • 2011-06-30
      • 2014-09-17
      • 2011-10-25
      • 1970-01-01
      • 2015-01-06
      相关资源
      最近更新 更多