RegEx,StringBuilder和大对象堆碎片
如何在不引起LOH碎片的情况下在大字符串中运行大量RegExes(以查找匹配项)?
它是.NET Framework 4.0所以我使用StringBuilder
所以它不在LOH中,但是一旦我需要在它上面运行RegEx,我必须调用StringBuilder.ToString()
,这意味着它将在LOH中。
有没有解决这个问题的方法? 拥有一个长期运行的应用程序几乎不可能处理大字符串和像这样的RegExes。
解决这个问题的想法:
在考虑这个问题时,我想我发现了一个肮脏的解决方案。
在给定的时间我只有5个字符串,这5个字符串(大于85KB)将传递给RegEx.Match
。
由于碎片发生是因为新对象不适合LOH中的空白空间,这应该可以解决问题:
-
PadRight
所有字符串最多。 接受大小,比方说1024KB(我可能需要用StringBuider
来做这个) - 通过这样做,所有新字符串将适合已经清空的内存,因为先前的字符串已经超出范围
- 不会有任何碎片,因为对象大小总是相同的,因此我只会在给定时间分配1024 * 5,并且LOH中的这些空间将在这些字符串之间共享。
我想这个设计的最大问题是如果其他大对象在LOH中分配这个位置会导致应用程序分配大量1024 KB字符串,甚至更糟糕的碎片。 但是,如果没有实际创建一个不在固定内存地址中的新字符串,我怎样才能将固定字符串发送到RegEx?
关于这个理论的任何想法? (不幸的是我无法轻易地重现问题,我通常会尝试使用内存分析器来观察更改,并且不确定我可以为此编写哪种隔离测试用例)
好的,这是我尝试以一种相当通用的方式解决这个问题,但有一些明显的局限性。 由于我没有在任何地方看到这个建议,每个人都抱怨LOH Fragmentation我想分享代码以确认我的设计和假设是正确的。
理论:
- 创建一个共享的大量StringBuilder(这是为了存储从我们从流中读取的大字符串) –
new StringBuilder(ChunkSize * 5);
- 创建一个庞大的String(必须大于最大可接受的大小),应该用空的空间初始化。 – 新字符串(”,ChunkSize * 10);
- 将字符串对象固定到内存中,这样GC就不会乱用它。
GCHandle.Alloc(pinnedText, GCHandleType.Pinned)
。 即使LOH对象通常被固定,这似乎也改善了性能。 也许是因为代码unsafe
- 将流读入共享的StringBuilder,然后通过使用索引器将其不安全地复制到pinnedText
- 将pinnedText传递给RegEx
通过这种实现,下面的代码就像没有LOH分配一样工作。 如果我切换到new string(' ')
分配而不是使用静态StringBuilder
或使用StringBuilder.ToString()
代码可以在崩溃与outofmemory exception
之前分配300%的内存
我还使用内存分析器确认了结果,在此实现中没有LOH碎片。 我仍然不明白为什么RegEx不会引起任何意外问题。 我还测试了不同且昂贵的RegEx模式,结果相同,没有碎片。
码:
https://pastebin.com/ZuuBUXk3
using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; namespace LOH_RegEx { internal class Program { private static List storage = new List (); private const int ChunkSize = 100000; private static StringBuilder _sb = new StringBuilder(ChunkSize * 5); private static void Main(string[] args) { var pinnedText = new string(' ', ChunkSize * 10); var sourceCodePin = GCHandle.Alloc(pinnedText, GCHandleType.Pinned); var rgx = new Regex("A", RegexOptions.CultureInvariant | RegexOptions.Compiled); try { for (var i = 0; i < 30000; i++) { //Simulate that we read data from stream to SB UpdateSB(i); CopyInto(pinnedText); var rgxMatch = rgx.Match(pinnedText); if (!rgxMatch.Success) { Console.WriteLine("RegEx failed!"); Console.ReadLine(); } //Extra buffer to fragment LoH storage.Add(new string('z', 50000)); if ((i%100) == 0) { Console.Write(i + ","); } } } catch (Exception ex) { Console.WriteLine(ex.ToString()); Console.WriteLine("OOM Crash!"); Console.ReadLine(); } } private static unsafe void CopyInto(string text) { fixed (char* pChar = text) { int i; for (i = 0; i < _sb.Length; i++) { pChar[i] = _sb[i]; } pChar[i + 1] = ' '; } } private static void UpdateSB(int extraSize) { _sb.Remove(0,_sb.Length); var rnd = new Random(); for (var i = 0; i < ChunkSize + extraSize; i++) { _sb.Append((char)rnd.Next(60, 80)); } } } }
您可以在某个时间点卸载的AppDomain中完成工作吗?
一种替代方案是找到一种在非基于arrays的数据结构上执行reg-ex匹配的方法。 不幸的是,一个快速的谷歌在基于流的reg-ex库方面并没有带来太多。 我猜想reg-ex算法需要进行大量的后向跟踪,而流不支持。
你绝对需要正则表达式的全部function吗? 您是否可以实现自己更简单的搜索function,这些function可以在85kb以下的字符串链接列表上工作?
此外,如果长时间保持大对象引用,LOH碎片只会导致问题。 如果你不断创造和摧毁它们,那么LOH就不应该成长。
FWIW,我认为RedGate ANTS内存分析器非常擅长跟踪LOH中的对象和碎片级别。
上述就是C#学习教程:RegEx,StringBuilder和大对象堆碎片分享的全部内容,如果对大家有所用处且需要了解更多关于C#学习教程,希望大家多多关注—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/cdevelopment/992282.html