RegEx, StringBuilder и фрагмент большого массива объектов

Как я могу запустить много RegExes (чтобы найти совпадения) в больших строках, не вызывая фрагментацию LOH?

Это .NET Framework 4.0, поэтому я использую StringBuilder, поэтому он не в LOH, но как только мне нужно запустить RegEx, мне нужно позвонить StringBuilder.ToString(), что означает, что он будет в LOH.

Есть ли какое-либо решение этой проблемы? Практически невозможно иметь длинное приложение, которое имеет дело с большими строками и RegExes, как это.

Идея решить эту проблему:

Задумываясь об этой проблеме, я думаю, что нашел грязное решение.

В данный момент у меня есть только 5 строк, и эти 5 строк (больше 85 КБ) будут переданы на RegEx.Match.

Поскольку фрагментация происходит из-за того, что новые объекты не подходят для пустых пространств в LOH, это должно решить проблему:

  • PadRight все строки до макс. принятый размер, допустим, 1024 КБ (мне может понадобиться сделать это с помощью StringBuider)
  • Таким образом, все новые строки будут соответствовать уже освобожденной памяти, поскольку предыдущая строка уже выходит за рамки
  • Не будет никакой фрагментации, потому что размер объекта всегда одинаковый, поэтому я буду выделять только 1024 * 5 в данный момент времени, и это пространство в LOH будет разделяться между этими строками.

Я полагаю, что самая большая проблема с этим дизайном происходит, если другие большие объекты выделяют это местоположение в LOH, что приведет к тому, что приложение будет выделять много строк 1024 КБ, возможно, с еще более худшей фрагментацией. Оператор fixed может помочь, однако, как я могу отправить фиксированную строку в RegEx, фактически не создав новую строку, которая не находится в адресе фиксированной памяти?

Любые идеи об этой теории? (К сожалению, я не могу легко воспроизвести проблему, я вообще пытаюсь использовать профилировщик памяти для наблюдения за изменениями и не уверен, какой изолированный тестовый сценарий я могу написать для этого)

Ответ 1

ОК, вот моя попытка решить эту проблему довольно общим образом, но с некоторыми очевидными ограничениями. Поскольку я не видел этот совет нигде, и все скулят о LOH Fragmentation, я хотел бы поделиться кодом, чтобы подтвердить, что мой дизайн и предположения верны.

Теория:

  • Создайте общий массив StringBuilder (это для хранения больших строк, которые читаются из чтения из потоков) - new StringBuilder(ChunkSize * 5);
  • Создание массивной строки (должно быть больше, чем максимально допустимый размер), должно быть инициализировано пустым пространством. - новая строка ('', ChunkSize * 10);
  • Вставьте строковый объект в память, чтобы GC не возился с ним. GCHandle.Alloc(pinnedText, GCHandleType.Pinned). Хотя объекты LOH обычно закреплены, это, по-видимому, улучшает производительность. Возможно, из-за unsafe code
  • Прочитайте поток в общий 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));
            }
        }
    }
}

Ответ 2

Вы можете выполнять свою работу в AppDomain, который выгружается в определенные моменты времени?

Ответ 3

Один из альтернатив - найти какой-то способ выполнения совпадений рег-ex в структуре данных, основанной не на массиве. К сожалению, быстрый Google не вызвал многого с точки зрения библиотек reg-ex, основанных на потоках. Я бы предположил, что для алгоритма reg-ex потребуется много обратного отслеживания, которое не поддерживается потоками.

Вам абсолютно нужна полная сила регулярных выражений? Могли бы вы, возможно, реализовать свои собственные более простые функции поиска, которые могли бы работать с связанными списками строк под 85kb?

Кроме того, фрагментация LOH действительно вызывает проблемы, если вы держитесь за ссылки на большие объекты в течение длительных периодов времени. Если вы постоянно создаете и уничтожаете их, LOH не должен расти.

FWIW, я создаю RedGate ANTI memory profiler, очень хорошо отслеживая объекты в LOH и уровни фрагментации.