RegEx,StringBuilder和大对象堆碎片

时间:2011-11-05 13:45:52

标签: c# regex memory .net-4.0 large-object-heap

如何在不引起LOH碎片的情况下在大字符串中运行大量RegExes(以查找匹配项)?

这是.NET Framework 4.0,所以我使用StringBuilder所以它不在LOH中,但是只要我需要在其上运行RegEx,我就必须调用StringBuilder.ToString(),这意味着它在LOH。

这个问题有解决办法吗?一个长期运行的应用程序几乎不可能处理大字符串和这样的RegExes。

解决此问题的想法:

在考虑这个问题时,我想我发现了一个肮脏的解决方案。

在给定时间我只有5个字符串,这5个字符串(大于85KB)将传递给RegEx.Match

由于碎片发生是因为新对象不适合LOH中的空白空间,这应该可以解决问题:

  1. PadRight所有字符串最多接受的大小,比方说1024KB(我可能需要用StringBuider
  2. 通过这样做,所有新字符串都适合已经清空的内存,因为前一个字符串已经超出范围
  3. 不会有任何碎片,因为对象大小总是相同的,因此我只会在给定时间分配1024 * 5,并且LOH中的这些空间将在这些字符串之间共享。
  4. 我认为这个设计的最大问题是如果其他大对象在LOH中分配这个位置会导致应用程序分配大量1024 KB字符串,甚至可能会出现更糟糕的碎片。 fixed语句可能有帮助但是如何在不实际创建不在固定内存地址中的新字符串的情况下向RegEx发送固定字符串?

    关于这个理论的任何想法? (不幸的是我无法轻易地重现问题,我通常会尝试使用内存分析器来观察更改,并且不确定我可以为此编写哪种隔离测试用例)

3 个答案:

答案 0 :(得分:7)

好的,这是我尝试以相当通用的方式解决这个问题,但有一些明显的局限性。由于我没有在任何地方看到这个建议,每个人都抱怨LOH Fragmentation我想分享代码以确认我的设计和假设是正确的。

<强>理论值:

  1. 创建一个共享的大量StringBuilder(这是为了存储我们从流中读取的大字符串) - new StringBuilder(ChunkSize * 5);
  2. 创建一个庞大的字符串(必须大于最大可接受的大小),应该用空格空间初始化。 - 新字符串('',ChunkSize * 10);
  3. 将字符串对象固定到内存中,这样GC就不会乱用它。 GCHandle.Alloc(pinnedText, GCHandleType.Pinned)。即使LOH对象通常被固定,这似乎也改善了性能。也许是因为unsafe代码
  4. 将流读入共享StringBuilder,然后通过使用索引器
  5. 将其复制到pinnedText
  6. 将pinnedText传递给RegEx
  7. 通过这种实现,下面的代码就像没有LOH分配一样工作。如果我切换到new string(' ')分配而不是使用静态StringBuilder或使用StringBuilder.ToString()代码,则可以在使用outofmemory exception <崩溃之前分配减少300%的内存 / p>

    我还使用内存分析器确认了结果,在此实现中没有LOH碎片。我仍然不明白为什么RegEx不会引起任何意外问题。我还测试了不同且昂贵的RegEx模式,结果相同,没有碎片。

    <强>代码:

    http://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<string> storage = new List<string>();
            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] = '\0';
                }
            }
    
            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));
                }
            }
        }
    }
    

答案 1 :(得分:0)

您可以在某个时间点卸载的AppDomain中完成工作吗?

答案 2 :(得分:0)

一种替代方案是找到一种在非基于阵列的数据结构上执行reg-ex匹配的方法。不幸的是,快速谷歌在基于流的reg-ex库方面并没有带来太多。我猜想reg-ex算法需要进行大量的后向跟踪,而流不支持。

你绝对需要正则表达式的全部功能吗?您是否可以实现自己更简单的搜索功能,这些功能可以在85kb以下的字符串链接列表上工作?

此外,如果长时间保持大对象引用,LOH碎片只会导致问题。如果你经常创造和摧毁它们,那么LOH就不应该成长。

FWIW,我认为RedGate ANTS memory profiler非常善于追踪LOH中的物体和碎片水平。