如何在大字符串中运行大量 RegEx(以查找匹配项)而不导致 LOH 碎片?
它是 .NET Framework 4.0,所以我使用的是 StringBuilder
,所以它不在 LOH 中,但是只要我需要在其上运行 RegEx,我就必须调用 StringBuilder.ToString( )
这意味着它将在 LOH 中。
这个问题有什么解决办法吗?像这样处理大字符串和正则表达式的长时间运行的应用程序几乎是不可能的。
解决这个问题的思路:
在思考这个问题时,我想我找到了一个肮脏的解决方案。
在给定时间我只有 5 个字符串,这 5 个字符串(大于 85KB)将被传递给 RegEx.Match
。
由于新对象不适合 LOH 中的空白空间而发生碎片,这应该可以解决问题:
PadRight
将所有字符串设为最大值。可接受的大小,假设为 1024KB(我可能需要使用StringBuider
来执行此操作)- 通过这样做,所有新字符串都将适合已清空的内存,因为之前的字符串已经超出范围
- 不会有任何碎片,因为对象大小始终相同,因此我只会在给定时间分配 1024*5,而 LOH 中的这些空间将在这些字符串之间共享。
我想这个设计的最大问题是如果其他大对象在 LOH 中分配这个位置会导致应用程序分配大量 1024 KB 的字符串可能会产生更严重的碎片。 fixed
语句可能会有所帮助,但是如何在不实际创建不在固定内存地址中的新字符串的情况下将固定字符串发送到 RegEx?
对这个理论有什么想法吗? (不幸的是我不能轻易地重现这个问题,我通常会尝试使用内存分析器来观察变化,并且不确定我可以为此编写什么样的隔离测试用例)
最佳答案
好的,这是我尝试以一种相当通用的方式解决这个问题,但有一些明显的限制。由于我在任何地方都没有看到这个建议,而且每个人都在提示 LOH 碎片,所以我想分享代码以确认我的设计和假设是正确的。
理论:
- 创建一个共享的海量StringBuilder(这是存储我们从流中读取的大字符串) -
new StringBuilder(ChunkSize * 5);
- 创建一个大字符串(必须大于最大可接受的大小),应该用空白空间初始化。 - 新字符串(' ', ChunkSize * 10);
- 将字符串对象固定到内存中,这样 GC 就不会弄乱它。
GCHandle.Alloc(pinnedText, GCHandleType.Pinned)
。尽管 LOH 对象通常是固定的,但这似乎可以提高性能。可能是因为unsafe
代码 - 将流读入共享 StringBuilder,然后使用索引器将其不安全地复制到 pinnedText
- 将 pinnedText 传递给 RegEx
有了这个实现,下面的代码就像没有 LOH 分配一样工作。如果我切换到 new string(' ')
分配而不是使用静态 StringBuilder
或使用 StringBuilder.ToString()
代码可以分配 outofmemory 异常
我还使用内存分析器确认了结果,即在此实现中没有 LOH 碎片。我仍然不明白为什么 RegEx 不会导致任何意外问题。我还使用不同且昂贵的 RegEx 模式进行了测试,结果是相同的,没有碎片。
代码:
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));
}
}
}
}
关于c# - RegEx、StringBuilder 和大对象堆碎片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8020550/