RegEx 和不同框架版本的内存使用和已知问题

Posted

技术标签:

【中文标题】RegEx 和不同框架版本的内存使用和已知问题【英文标题】:Memory usage and known issues with RegEx and different Framework versions 【发布时间】:2012-02-21 17:58:00 【问题描述】:

我们在 .Net 4.0 中创建了一个 Windows 服务,该服务解析由逗号分隔值行组成的大型文本文件(数百万行,值在 5-10 之间),这里没问题,我们可以阅读行,将它们拆分为键/值集合并处理值。为了验证值,我们使用 Data Paralellism 将 Values(基本上是特定格式的值数组)传递给对单个值执行 RegEx 验证的方法。

到目前为止,我们使用的是静态正则表达式,而不是静态 RegEx.IsMatch 方法,而是一个静态 RegEx 属性,其中 RegexOption 定义为 RegexOptions.Compiled,如下所述。

private static Regex clientIdentityRegEx = new Regex("^[0-9]4,9$", RegexOptions.Compiled);

使用这种方法,我们有一个非常标准的内存占用,随着每行中值的数量增加,内存略微增加,所花费的时间或多或少与总行数成线性关系。

为了让正则表达式能够在不同框架版本的其他项目中使用,我们最近将静态 RegEx 属性移动到现在使用 .Net 2.0 CLR 编译的通用实用程序项目(实际的正则表达式没有改变),暴露的 RegEx 属性数量从 25 个左右增加到了 60 个左右。自从这样做以来,我们开始遇到内存问题,内存增加了原始项目的 3 倍或更多倍。当我们分析正在运行的服务时,我们可以看到内存似乎从 RegEx.IsMatch 中“泄漏”,不是任何特定的 RegEx,而是根据调用的不同而有所不同。

我在 BCL 团队之一的旧 MSDN blog post 上发现了以下与 .Net 1.0/1.1 RegEx 相关的评论。

但是,应该提到的编译成本甚至更高。使用 Reflection.Emit 发出 IL 会加载大量代码并使用大量内存,而这不是您永远无法取回的内存。此外。在 v1.0 和 v1.1 中,我们永远无法释放我们生成的 IL,这意味着您使用此模式泄漏了内存。我们已经在 Whidbey 中解决了这个问题。但最重要的是,您应该只将此模式用于您知道将重复使用的有限表达式集。

我要补充的是,我们已经分析了“大多数”常见的 RegEx 调用,并且无法单独复制问题。

这是 .Net 2.0 CLR 的已知问题吗?

作者在文章中声明“但最重要的是,您应该只将此模式用于您知道会重复使用的有限表达式集”,以这种方式使用的表达式的数量可能是多少,这可能是一个原因吗?

更新:根据@Henk Holterman 的回答,除了使用纯粹的蛮力音量和参数格式?

答案: Hanks 对“场景需要有限、固定数量的 RegEx 对象”的回答非常准确,我们将静态 RegEx 添加到类中,直到我们隔离了表达式随着内存使用量的显着增加,这些被迁移到单独的静态类中,这似乎解决了一些内存问题。

看来,虽然我无法确认这一点,但 .Net 2.0 CLR 和 .Net 4.0 CLR 之间编译的 RegEx 使用之间存在差异,因为仅针对 .Net 4.0 框架编译时不会出现内存问题。 (任何确认?)

【问题讨论】:

好的,你重构了你的代码并且大大提高了 RegEx 的数量。你能回去一次做一个吗? 分析器到底告诉你什么?关于内存泄漏的类型? 是的,我们今天晚些时候完成了这项工作,并且将项目编译到 .Net 2.0 CLR 后,内存增加了,而之前使用的 RegEx 数量完全相同,似乎与我们添加了多少,但它们是如何被调用的以及表达式的类型。 内存显示堆栈内存大幅增加,这似乎是更大的问题,并且堆内存增加(比正常情况高出约 40%)。反过来编辑) 【参考方案1】:

该场景需要有限的、固定数量的 RegEx 对象。那不应该泄漏。您应该验证在新情况下 RegEx 对象仍在被重用。

另一种可能性是表达式数量增加(从 25 个增加到 60 个)。可能只是其中一个稍微复杂一点,导致过度回溯吗?

【讨论】:

以上是关于RegEx 和不同框架版本的内存使用和已知问题的主要内容,如果未能解决你的问题,请参考以下文章

.net 框架中值类型和引用类型的内存分配

.NET 5 中的 C# RegEx 行为与其他版本不同

Flash Builder 4 Profiler:如何发现导致已知内存增加的对象?

源码解读·RT-Thread操作系统内存管理之内存池

C++ 中的 Seastar 框架是不是允许用户在不同的线程中分配不同大小的内存?

Python - 多处理和共享内存