.NET GC 停止桌面应用程序 - 性能问题
.NET GC Stalling Desktop Application - Performance Issue
我正在开发一个大型 windows 桌面应用程序,它以项目文件的形式存储大量数据。我们有自定义的 ORM 和序列化,可以有效地从 CSV 格式加载对象数据。此任务由多个线程 运行 在并行处理多个文件时执行。我们的大型项目可以包含数百万甚至更多的对象,它们之间有很多关系。
最近我的任务是改善项目打开性能,这对于非常大的项目来说是恶化的。分析后发现大部分时间都花在了垃圾收集 (GC) 上。
我的理论是,由于大量非常快速的分配,GC 被饿死,推迟了很长时间,然后当它最终启动时需要很长时间才能完成工作。两个相互矛盾的事实进一步证实了这个想法:
- 优化反序列化代码以更快地工作只会让事情变得更糟
- 在关键位置插入
Thread.Sleep
调用使加载速度更快
下面是 7 个第 2 代收集和大量 GC 时间百分比的缓慢加载示例。
下面是代码中带有休眠期的快速加载示例,可让 GC 有一些时间。在这种情况下,我们有 19 个第 2 代集合,也是第 0 代和第 1 代集合数量的两倍多。
所以,我的问题是如何防止这种 GC 饥饿?添加 Thread.Sleep
看起来很傻,而且很难在正确的位置猜测正确的毫秒数。我的另一个想法是使用 GC.Collect
,但这也带来了放置它们的数量和位置的困难。还有其他想法吗?
根据评论,我猜您在 CSV 解析过程中进行了大量 String.Substring()
操作。这些中的每一个都会创建一个新的字符串实例,我敢打赌您在将其进一步解析为整数或日期或您需要的任何内容后将其丢弃。您几乎肯定需要开始考虑使用不同的持久性机制(CSV 有很多您无疑知道的缺点),但与此同时您将想要研究不分配子字符串的解析器版本。如果深入研究 Int32.TryParse 的代码,您会发现它会进行一些字符迭代以避免分配更多字符串。我敢打赌,您可以花一个小时编写一个带有 start
和 end
参数的版本,然后您可以将带偏移量的整行传递给它们,并避免执行子字符串调用来获取单个字段值。这样做将为您节省数百万的分配。
因此,这似乎是一个 .NET 错误,而不是 GC 饥饿。此问题 中描述的解决方法和答案非常适用。我通过切换到 GC 服务器模式获得了最佳结果。
但是请注意,我在 .NET 4.5.2 中遇到了这个问题。将添加修补程序 link 如果有的话。
我正在开发一个大型 windows 桌面应用程序,它以项目文件的形式存储大量数据。我们有自定义的 ORM 和序列化,可以有效地从 CSV 格式加载对象数据。此任务由多个线程 运行 在并行处理多个文件时执行。我们的大型项目可以包含数百万甚至更多的对象,它们之间有很多关系。
最近我的任务是改善项目打开性能,这对于非常大的项目来说是恶化的。分析后发现大部分时间都花在了垃圾收集 (GC) 上。
我的理论是,由于大量非常快速的分配,GC 被饿死,推迟了很长时间,然后当它最终启动时需要很长时间才能完成工作。两个相互矛盾的事实进一步证实了这个想法:
- 优化反序列化代码以更快地工作只会让事情变得更糟
- 在关键位置插入
Thread.Sleep
调用使加载速度更快
下面是 7 个第 2 代收集和大量 GC 时间百分比的缓慢加载示例。
下面是代码中带有休眠期的快速加载示例,可让 GC 有一些时间。在这种情况下,我们有 19 个第 2 代集合,也是第 0 代和第 1 代集合数量的两倍多。
所以,我的问题是如何防止这种 GC 饥饿?添加 Thread.Sleep
看起来很傻,而且很难在正确的位置猜测正确的毫秒数。我的另一个想法是使用 GC.Collect
,但这也带来了放置它们的数量和位置的困难。还有其他想法吗?
根据评论,我猜您在 CSV 解析过程中进行了大量 String.Substring()
操作。这些中的每一个都会创建一个新的字符串实例,我敢打赌您在将其进一步解析为整数或日期或您需要的任何内容后将其丢弃。您几乎肯定需要开始考虑使用不同的持久性机制(CSV 有很多您无疑知道的缺点),但与此同时您将想要研究不分配子字符串的解析器版本。如果深入研究 Int32.TryParse 的代码,您会发现它会进行一些字符迭代以避免分配更多字符串。我敢打赌,您可以花一个小时编写一个带有 start
和 end
参数的版本,然后您可以将带偏移量的整行传递给它们,并避免执行子字符串调用来获取单个字段值。这样做将为您节省数百万的分配。
因此,这似乎是一个 .NET 错误,而不是 GC 饥饿。此问题
但是请注意,我在 .NET 4.5.2 中遇到了这个问题。将添加修补程序 link 如果有的话。