好的,这是我尝试以一种相当通用的方式解决这个问题,但有一些明显的限制。由于我在任何地方都没有看到这个建议,而且每个人都在抱怨 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()
代码可以在崩溃之前分配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));
}
}
}
}