RegEx, StringBuilder和大对象堆碎片

本文关键字:对象 碎片 StringBuilder RegEx | 更新日期: 2023-09-27 18:13:39

我如何在大字符串中运行大量RegExes(查找匹配)而不会导致LOH碎片?

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

这个问题有解决办法吗?要让一个长时间运行的应用程序处理像这样的大字符串和regex,实际上是不可能的。

一个解决这个问题的想法:

在思考这个问题的时候,我想我找到了一个肮脏的解决方案。

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

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

  1. PadRight所有字符串的最大值。可接受的大小,我们设为1024KB(我可能需要使用StringBuider)
  2. 通过这样做,所有新字符串将适合已经清空的内存,因为之前的字符串已经超出了作用域
  3. 不会有任何碎片,因为对象大小总是相同的,因此我将只分配1024*5在给定的时间,这些空间在LOH将在这些字符串之间共享。

我认为这种设计的最大问题是如果其他大对象在LOH中分配此位置会发生什么,这将导致应用程序分配大量1024 KB的字符串,可能会产生更严重的碎片。fixed语句可能会有所帮助,但是我怎么能发送一个固定的字符串RegEx实际上没有创建一个新的字符串不位于固定的内存地址?

对这个理论有什么看法吗?(不幸的是,我不能轻易地重现这个问题,我通常试图使用内存分析器来观察变化,不确定我可以为此编写什么样的隔离测试用例)

RegEx, StringBuilder和大对象堆碎片

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

理论:

  1. 创建一个共享的大型StringBuilder(用于存储从我们从流中读取的大字符串)- new StringBuilder(ChunkSize * 5);
  2. 创建一个巨大的字符串(必须大于max)。可接受的大小),应该用空白空间初始化。- new string(' ', ChunkSize * 10);
  3. 将字符串对象引脚到内存中,这样GC就不会干扰它。GCHandle.Alloc(pinnedText, GCHandleType.Pinned)。尽管LOH对象通常是固定的,但这似乎提高了性能。可能是因为unsafe代码
  4. 读取流到共享StringBuilder,然后使用indexers不安全复制到pinnedText
  5. 传递pinnedText给RegEx
有了这个实现,下面的代码就像没有LOH分配一样工作。如果我切换到new string(' ')分配而不是使用静态StringBuilder或使用StringBuilder.ToString()代码可以分配300%的内存在崩溃之前与outofmemory exception

我还用内存分析器确认了结果,在这个实现中没有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));
            }
        }
    }
}

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

一种替代方法是找到在非基于数组的数据结构上执行reg-ex匹配的某种方法。不幸的是,谷歌并没有提供太多基于流的reg-ex库。我猜regg -ex算法需要做大量的反向跟踪,这是流不支持的。

你绝对需要正则表达式的全部功能吗?您是否可以实现自己的更简单的搜索函数,可以在所有小于85kb的字符串链表上工作?

另外,LOH碎片只有在长时间持有大型对象引用时才会真正引起问题。如果你不断地创造和破坏它们,LOH就不会增长。

顺便说一下,我发现RedGate ANTS内存分析器非常擅长跟踪LOH中的对象和碎片级别。