【问题标题】:.NET GC Stalling Desktop Application - Performance Issue.NET GC 停止桌面应用程序 - 性能问题
【发布时间】:2015-11-03 16:22:00
【问题描述】:

我正在开发一个大型 Windows 桌面应用程序,它以项目文件的形式存储大量数据。我们有我们的自定义 ORM 和序列化来有效地从 CSV 格式加载对象数据。此任务由并行运行的多个线程执行,处理多个文件。我们的大型项目可以包含数百万甚至更多的对象,它们之间有许多关系。

最近,我的任务是改善大型项目的项目开放性能,这种性能会恶化。分析后发现,大部分时间都花在垃圾收集 (GC) 上。

我的理论是,由于大量非常快速的分配,GC 被饿死了,被推迟了很长时间,然后当它最终启动时,它需要很长时间才能完成工作。两个相互矛盾的事实进一步证实了这一想法:

  1. 优化反序列化代码以更快地工作只会让事情变得更糟
  2. 在战略位置插入 Thread.Sleep 呼叫可以加快负载速度

下面是第 7 代第 2 代收集的缓慢加载和大量 GC 时间的示例。

下面是代码中带有休眠期的快速加载示例,以允许 GC 一些时间。在这种情况下,我们有 19 个第 2 代集合,并且也是第 0 代和第 1 代集合的两倍多。

那么,我的问题是如何防止这种 GC 饥饿?添加Thread.Sleep 看起来很傻,很难在正确的位置猜出正确的毫秒数。我的另一个想法是使用GC.Collect,但这也带来了放置数量和放置位置的困难。还有其他想法吗?

【问题讨论】:

  • 我想知道 Thread.Sleep 如何/为什么让事情变得更快?快多少?
  • 我在创建大量重复字符串变量然后作为序列化的一部分取消引用的情况下看到了这种类型的行为。你使用什么样的序列化来加载项目文件?
  • 所有加载都是在后台线程上完成的,所以我假设 sleep 允许 GC 线程启动。从上面的图片你可以看到,一旦我们引入睡眠,我们会从 4 分钟缩短到 25 秒。减速以加快速度:)
  • GC 由分配触发。如果您在此期间休眠,则不会触发 GC。我发现 GC 饥饿理论的证据非常薄弱。
  • @HansPassant 在 cmets 中查找该问题的答案 - 看起来该错误已通过修补程序 stackoverflow.com/a/31999381/4450618 修复

标签: c# .net garbage-collection desktop-application


【解决方案1】:

根据 cmets,我猜您正在执行大量 String.Substring() 操作作为 CSV 解析的一部分。这些中的每一个都会创建一个新的字符串实例,我敢打赌你会在进一步将其解析为整数或日期或任何你需要的东西后将其丢弃。您几乎肯定需要开始考虑使用不同的持久性机制(CSV 有很多您无疑知道的缺点),但与此同时,您将要研究不分配子字符串的解析器版本。如果您深入研究 Int32.TryParse 的代码,您会发现它会进行一些字符迭代以避免分配更多的字符串。我敢打赌,您可以花一个小时编写一个带有startend 参数的版本,然后您可以将整行的偏移量传递给它们,并避免进行子字符串调用来获取各个字段值。这样做将为您节省数百万的分配。

【讨论】:

  • 我需要就高效 CSV 解析的主题提出单独的问题。问题在于 .NET 促使您使用字符串,因为您可以方便地使用 StreamReader.ReadLine、String.Split 和 Single.TryParse 都使用字符串。大多数可用的 CSV 解析器都可以做到这一点……希望有一个可变字符串,我可以一遍又一遍地重复使用。
【解决方案2】:

因此,这似乎是一个 .NET 错误,而不是 GC 饥饿。此问题Garbage Collection and Parallel.ForEach Issue After VS2015 Upgrade 中描述的解决方法和答案非常适用。通过切换到 GC 服务器模式,我得到了最好的结果。

但是请注意,我在 .NET 4.5.2 中遇到了这个问题。有的话会添加修复链接。

【讨论】:

    猜你喜欢
    • 2012-08-22
    • 1970-01-01
    • 2017-07-02
    • 2015-01-18
    • 1970-01-01
    • 1970-01-01
    • 2012-02-10
    • 1970-01-01
    • 2010-12-12
    相关资源
    最近更新 更多