【问题标题】:Performance optimizing use of generated XmlSerializer class生成的 XmlSerializer 类的性能优化使用
【发布时间】:2014-10-03 23:54:32
【问题描述】:

我们的应用程序正在读取一些 XML 文件。 XML 格式是固定的,因此我们可以使用XmlSerializer 轻松读取它们。

我使用此代码读取 XML 文件并将它们转换为类:

public static T FromXml<T>(this string xml) where T : class
{
    if (string.IsNullOrEmpty(xml))
    {
        return default(T);
    }

    XmlSerializer xmlserializer = new XmlSerializer(typeof(T));

    XmlTextReader textReader = new XmlTextReader(new StringReader(xml));
    textReader.Normalization = false;

    XmlReaderSettings settings = new XmlReaderSettings();

    T value;

    using (XmlReader reader = XmlReader.Create(textReader, settings))
    {
        value = (T)xmlserializer.Deserialize(reader);
    }

    return value;
}

但是,存在一些性能问题。第一次调用此代码时,使用 T 的特定类型时,XmlSerializer 会生成一个 Project.XmlSerializer.dll 文件。

这很好,但要花费一些宝贵的毫秒时间(在我的情况下大约是 900 毫秒)。这可以通过使用XML Serializer Generator (sgen) 在正手上生成该程序集来规避。这将时间减少到大约一半。主要是由于程序集的读取和反射。

我想进一步优化这一点,将XmlSerializer 类带入实际类所在的程序集中,但我找不到让XmlSerializer 知道不要阅读的方法外部程序集,但使用当前程序集的序列化程序。

任何想法如何做到这一点或使这项工作的替代方法? (我无法预加载它们,因为大多数序列化的类都是在启动时使用的)


使用 ANTS Profiler 的分析(来自其他机器的指标,但模式相同):

简单。大多数时间(300 毫秒 + 400 毫秒 = 700 毫秒)都浪费在生成和加载 XmlSerializer 程序集的过程中。

使用 sgen 生成程序集。大多数时间(336 毫秒)都在加载 XmlSerializer 程序集时丢失。

当在项目中包含程序集的实际源代码并直接调用序列化程序时,动作会下降到 456 毫秒(第一次是 1 秒,第二次是 556 毫秒)。

【问题讨论】:

  • 与问题无关:您不需要 reader.Close();,因为 using 语句通过 Dispose 为您执行此操作。
  • 我无法理解您在开头的最后一段中的意思我想通过引入 XmlSerializer 类来进一步优化它... 你能说清楚一点吗?那我试试看:)
  • 如果加载 dll 是您感觉的唯一问题。您可以打开文件并立即关闭它(应用程序启动后立即在另一个线程中)(如果您知道路径)。操作系统会将文件缓存在缓存中,以便下次尝试读取文件(通过 clr)将很快。忘记我是否在不理解你的情况下说了一些愚蠢的话:(
  • 单次执行是您的应用正常运行的方式吗?如果生成/加载程序集需要很长时间,并且您的应用程序多次使用 FromXml,您应该进行更实际的性能测试,在其中反序列化各种数据以查看实际反序列化性能。
  • 用户设置在启动时读取并在关闭时保存。还有一些其他的 xml 文件通常具有相同的模式。 (顺便说一句,第二次确实很快,但我想尽量减少启动时间)

标签: c# .net performance xml-serialization


【解决方案1】:

注意:OP 发布了一个示例配置:http://pastebin.com/d67nch3R

根据示例配置和您遇到的问题类型,有几种蛮力方法,几乎​​可以保证做到这一点,两者都归结为完全放弃 XML 序列化程序

1 号路线

放弃 XML 序列化并使用 XDocument 从 XML 中获取数据。

2 号路线

使用 json 和 Newtonsoft Json 来存储和加载配置。它的性能应该比 XML Serializer 好很多

对应的示例 json 如下所示:

{
  "Connections": {
    "-default": "Local\\SqlServer",
    "-forcedefault": "false",
    "group": {
      "-name": "Local",
      "connection": {
        "-name": "SqlServer",
        "database": {
          "-provider": "SqlServer",
          "-connectionString": "blah"
        }
      }
    }
  },
  "LastLanguage": "en",
  "UserName": "un",
  "SavePassword": "true",
  "AutoConnect": "false",
  "Password": "someObfuscatedHashedPassword==",
  "ConnectionName": "Somewhere\\Database",
  "LastAvailableBandwidth": "0",
  "LastAvailableLatency": "0",
  "DateLastConnectionSuccesful": "2014-08-13T15:21:35.9663654+02:00"
}

并加载它:

UserSettings settings = JsonConvert.DeserializeObject<UserSettings>(File.ReadAllText("settings.json"))

【讨论】:

  • 您的问题是启动时间,答案本质上是特定的
  • 我尝试了您的 JSON 方法。在运行了几个分析会话之后,似乎 JSON 比 XmlSerializer 方法慢了一点(JSON = 650ms vs XML 556ms(使用第二个时间,因为它更适合 JSON)。
  • 发生了一些奇怪的事情,因为在 LinqPad 中,我在 0.035 秒内加载了 json
  • 你是对的。我完全错过了一些东西。我过于关注 ANTS 的结果。该程序单独运行时需要 14 毫秒(经过我的优化)。其他 600 毫秒是 ANTS 的开销(应该被过滤掉,但没有)。实现 JSON 并没有太大的改变,我想用 XML 进行交易。
  • 感谢您的宝贵时间,我会看看如何处理赏金。您很快就会注意到它。
【解决方案2】:

除非您在应用程序启动时进行序列化,否则一种方法是强制 CLR 加载甚至提前编译您正在使用的任何类,可能在一个线程中,一旦您在后台运行'已经启动了你的应用程序。

类似的东西,例如:

foreach (Assembly a in assembliesThatShouldBeCompileed)
    foreach (Type type in a.GetTypes())
        if (!type.IsAbstract && type.IsClass)
        {
            foreach (MethodInfo method in type.GetMethods(
                                BindingFlags.DeclaredOnly |
                                BindingFlags.NonPublic |
                                BindingFlags.Public |
                                BindingFlags.Instance |
                                BindingFlags.Static))
            {
                if (method.ContainsGenericParameters || 
                    method.IsGenericMethod || 
                    method.IsGenericMethodDefinition)
                    continue;

                if ((method.Attributes & MethodAttributes.PinvokeImpl) > 0)
                    continue;

                System.Runtime.CompilerServices
                   .RuntimeHelpers.PrepareMethod(method.MethodHandle);
            }
        }

然而,奇怪的是,您的分析似乎表明,如果 SGEN 的代码位于单独的程序集中,则没有太大区别,而加载似乎是瓶颈。我想知道当它们在同一个程序集中的情况下,图表看起来如何?

【讨论】:

  • 感谢您的回答。如果我在启动时不需要它们,这确实可行,但我确实需要...... :(
  • @Patrick:一个细节,探查器图表如何查找“1.7s”案例?
  • 我更新了问题。奇怪的是,大部分时间都是在读取配置文件,而不是创建序列化程序本身。
  • 找到实际位置:它尝试读取&lt;system.xml.serialization&gt; 部分。
  • 咕噜,我完全搞砸了。请参阅我在另一个答案下的最后评论。你一直很有帮助,就像其他人一样。我认为没有必要在这方面花更多时间(双方)。然后剩下的是赏金。既然您有很多代表,我想建议您接受您的回答,但将赏金奖励给其他人。这对你公平吗?
猜你喜欢
  • 1970-01-01
  • 2011-07-13
  • 2014-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-03
  • 2012-03-19
  • 1970-01-01
相关资源
最近更新 更多