12

如何在大字符串中运行大量正则表达式(以查找匹配项)而不会导致 LOH 碎片?

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

这个问题有什么解决办法吗?像这样处理大字符串和正则表达式的长时间运行的应用程序几乎是不可能的。

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

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

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

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

  1. PadRight所有字符串到最大值。接受的大小,比如说 1024KB(我可能需要这样做StringBuider
  2. 通过这样做,所有新字符串都将适合已清空的内存,因为先前的字符串已经超出范围
  3. 不会有任何碎片,因为对象大小始终相同,因此我只会在给定时间分配 1024*5,并且 LOH 中的这些空间将在这些字符串之间共享。

我想这个设计的最大问题是如果其他大对象在 LOH 中分配这个位置会导致应用程序分配大量 1024 KB 的字符串,可能会有更严重的碎片。fixed语句可能会有所帮助,但是如何在不实际创建不在固定内存地址中的新字符串的情况下将固定字符串发送到 RegEx?

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

4

3 回答 3

7

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

理论:

  1. 创建一个共享的大量 StringBuilder(这是存储从我们从流中读取的大字符串) -new StringBuilder(ChunkSize * 5);
  2. 创建一个巨大的字符串(必须大于最大可接受的大小),应该用空白空间初始化。- 新字符串(' ', ChunkSize * 10);
  3. 将字符串对象固定到内存,这样 GC 就不会弄乱它。GCHandle.Alloc(pinnedText, GCHandleType.Pinned). 尽管 LOH 对象通常是固定的,但这似乎可以提高性能。可能是因为unsafe代码
  4. 将流读入共享 StringBuilder,然后使用索引器将其不安全地复制到 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));
            }
        }
    }
}
于 2011-11-06T11:21:46.480 回答
0

一种替代方法是找到某种在非基于数组的数据结构上执行正则表达式匹配的方法。不幸的是,在基于流的正则表达式库方面,快速的谷歌并没有带来太多。我猜想 reg-ex 算法需要做很多回溯,而流不支持。

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

此外,如果您长时间持有大型对象引用,LOH 碎片只会导致问题。如果你不断地创造和摧毁它们,那么 LOH 就不应该增长。

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

于 2011-11-06T14:25:36.347 回答
0

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

于 2011-11-05T19:19:25.717 回答