Вопрос по c#, stringbuilder, regex – Кто-нибудь реализовал парсер Regex и / или Xml вокруг StringBuilders или Streams?

10

Я создаю клиент для стресс-тестирования, который отбивает серверы и анализирует ответы, используя столько потоков, сколько может собрать клиент. Я постоянно оказываюсь задушенным сборщиком мусора (и / или его отсутствием), и в большинстве случаев это сводится к строкам, которые я создаю только для того, чтобы передать их в процедуру синтаксического анализа Regex или Xml.

Если вы декомпилируете класс Regex, вы увидите, чтоinternally, он использует StringBuilders, чтобы делать почти все, но вы не можетеpass это струнный строитель; он услужливо погружается в приватные методы перед тем, как начать их использовать, поэтому методы расширения также не решат его. Вы находитесь в аналогичной ситуации, если хотите получить граф объектов из синтаксического анализатора в System.Xml.Linq.

Это не случай педантичной чрезмерной оптимизации заранее. Я посмотрел наRegex замены внутри StringBuilder вопрос и др. Я также профилировал свое приложение, чтобы увидеть, откуда берутся потолки, и используюRegex.Replace() сейчас действительно вносятся значительные издержки в цепочку методов, где я пытаюсь поразить сервер миллионами запросов в час и проверить ответы XML на наличие ошибок и встроенных диагностических кодов. Я уже избавился практически от любой другой неэффективности, которая ограничивает пропускную способность, и я даже сократил многие накладные расходы на Regex, расширив StringBuilder для выполнения поиска / замены с помощью подстановочных знаков, когда мне не нужны группы захвата или обратные ссылки, но мне кажется, что кто-то уже обернул пользовательскую утилиту синтаксического анализа Regex и Xml на основе StringBuilder (или, еще лучше, Stream).

Хорошо, так разглагольствовать, но я собираюсь сделать это сам?

Update: Я нашел обходной путь, который снизил пиковое потребление памяти с нескольких гигабайт до нескольких сотен мегабайт, поэтому я разместил его ниже. Я не добавляю его в качестве ответа, потому что а) я вообще ненавижу это делать и б) я все еще хочу выяснить, тратит ли кто-нибудь время, чтобы настроить StringBuilder для выполнения регулярных выражений (или наоборот), прежде чем я это сделаю.

В моем случае я не мог использовать XmlReader, потому что поток, который я принимаю, содержит недопустимый двоичный контент в определенных элементах. Чтобы разобрать XML, я должен очистить эти элементы. Ранее я использовал один статический скомпилированный экземпляр Regex для замены, и это потребляло память как безумный (я пытаюсь обработать ~ 300 10 КБ документов / сек). Изменение, которое резко сократило потребление, было:

I added the code from this StringBuilder Extensions article on CodeProject for the handy IndexOf method. I added a (very) crude WildcardReplace method that allows one wildcard character (* or ?) per invocation I replaced the Regex usage with a WildcardReplace() call to empty the contents of the offending elements

Это очень неуместно и проверено только настолько, насколько этого требуют мои собственные цели; Я бы сделал его более элегантным и мощным, но ЯГНИ и все такое, и я тороплюсь. Вот код:

/// <summary>
/// Performs basic wildcard find and replace on a string builder, observing one of two 
/// wildcard characters: * matches any number of characters, or ? matches a single character.
/// Operates on only one wildcard per invocation; 2 or more wildcards in <paramref name="find"/>
/// will cause an exception.
/// All characters in <paramref name="replaceWith"/> are treated as literal parts of 
/// the replacement text.
/// </summary>
/// <param name="find"></param>
/// <param name="replaceWith"></param>
/// <returns></returns>
public static StringBuilder WildcardReplace(this StringBuilder sb, string find, string replaceWith) {
    if (find.Split(new char[] { '*' }).Length > 2 || find.Split(new char[] { '?' }).Length > 2 || (find.Contains("*") && find.Contains("?"))) {
        throw new ArgumentException("Only one wildcard is supported, but more than one was supplied.", "find");
    } 
    // are we matching one character, or any number?
    bool matchOneCharacter = find.Contains("?");
    string[] parts = matchOneCharacter ? 
        find.Split(new char[] { '?' }, StringSplitOptions.RemoveEmptyEntries) 
        : find.Split(new char[] { '*' }, StringSplitOptions.RemoveEmptyEntries);
    int startItemIdx; 
    int endItemIdx;
    int newStartIdx = 0;
    int length;
    while ((startItemIdx = sb.IndexOf(parts[0], newStartIdx)) > 0 
        && (endItemIdx = sb.IndexOf(parts[1], startItemIdx + parts[0].Length)) > 0) {
        length = (endItemIdx + parts[1].Length) - startItemIdx;
        newStartIdx = startItemIdx + replaceWith.Length;
        // With "?" wildcard, find parameter length should equal the length of its match:
        if (matchOneCharacter && length > find.Length)
            break;
        sb.Remove(startItemIdx, length);
        sb.Insert(startItemIdx, replaceWith);
    }
    return sb;
}
Вы можете обнаружить, что сервер GC имеет все значение. Я видел, как это снижает накладные расходы GC с 80% до менее чем 10%. Это легко сделать. Jim Mischel
@ Андре, да, это, вероятно, хорошее предложение, я пока что просто избегал его из-за всей логики, которую я должен разгадать. Текущая стратегия состоит в том, чтобы асинхронно проанализировать все, получить требуемый граф объектов из ответа и выбросить его в MongoDB для более глубокого анализа позже. Поэтому я полагаю, что если я не предприму декомпиляцию всего, от чего зависит Regex, & amp; настраивая все, что нужно для вызова .Replace (), это следующий лучший вариант. Если никто не откашливает предварительно свернутое решение, думаю, мне придется принять это решение. Paul Smith
@JimMischel, я используюRegexOptions.Compiled и очень мало постоянных статических экземпляров Regex. Я еще не прошел хостинг сервера GC. Paul Smith
В вашем сценарии целесообразно сохранить необработанные данные и проанализировать их позже? Я видел какой-то анализ, в котором использовался этот подход ... Ortiga
Две оптимизации, о которых вы не упомянули, используютсяRegexOptions.Compiled для ваших регулярных выражений, и с помощью сборщика мусора сервера. Вы сделали оба этих? Jim Mischel

Ваш Ответ

3   ответа
0

эффективности. Любое количество ваших*с или?можно использовать. Тем не менее, ваш* сейчас и ваш? сейчас, На это ушло около трех дней работы, чтобы сделать его как можно более чистым. Вы даже можете ввести несколько запросов за один раз!

Пример использования:wildcard(new StringBuilder("Hello and welcome"), "hello✪w★l", "be") результаты в "становятся".

////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////// Search for a string/s inside 'text' using the 'find' parameter, and replace with a string/s using the replace parameter
// ✪ represents multiple wildcard characters (non-greedy)
// ★ represents a single wildcard character
public StringBuilder wildcard(StringBuilder text, string find, string replace, bool caseSensitive = false)
{
    return wildcard(text, new string[] { find }, new string[] { replace }, caseSensitive);
}
public StringBuilder wildcard(StringBuilder text, string[] find, string[] replace, bool caseSensitive = false)
{
    if (text.Length == 0) return text;          // Degenerate case

    StringBuilder sb = new StringBuilder();     // The new adjusted string with replacements
    for (int i = 0; i < text.Length; i++)   {   // Go through every letter of the original large text

        bool foundMatch = false;                // Assume match hasn't been found to begin with
        for(int q=0; q< find.Length; q++) {     // Go through each query in turn
            if (find[q].Length == 0) continue;  // Ignore empty queries

            int f = 0;  int g = 0;              // Query cursor and text cursor
            bool multiWild = false;             // multiWild is ✪ symbol which represents many wildcard characters
            int multiWildPosition = 0;          

            while(true) {                       // Loop through query characters
                if (f >= find[q].Length || (i + g) >= text.Length) break;       // Bounds checking
                char cf = find[q][f];                                           // Character in the query (f is the offset)
                char cg = text[i + g];                                          // Character in the text (g is the offset)
                if (!caseSensitive) cg = char.ToLowerInvariant(cg);
                if (cf != '★' && cf != '✪' && cg != cf && !multiWild) break;        // Break search, and thus no match is found
                if (cf == '✪') { multiWild = true; multiWildPosition = f; f++; continue; }              // Multi-char wildcard activated. Move query cursor, and reloop
                if (multiWild && cg != cf && cf != '★') { f = multiWildPosition + 1; g++; continue; }   // Match since MultiWild has failed, so return query cursor to MultiWild position
                f++; g++;                                                           // Reaching here means that a single character was matched, so move both query and text cursor along one
            }

            if (f == find[q].Length) {          // If true, query cursor has reached the end of the query, so a match has been found!!!
                sb.Append(replace[q]);          // Append replacement
                foundMatch = true;
                if (find[q][f - 1] == '✪') { i = text.Length; break; }      // If the MultiWild is the last char in the query, then the rest of the string is a match, and so close off
                i += g - 1;                                                 // Move text cursor along by the amount equivalent to its found match
            }
        }
        if (!foundMatch) sb.Append(text[i]);    // If a match wasn't found at that point in the text, then just append the original character
    }
    return sb;
}
1

переключил лицензию для своих основных библиотек на лицензию MIT X11, Если вам нужно создать библиотеку регулярных выражений, настроенную для производительности в вашем конкретном приложении, вы сможете начать с самого последнего кода изМоно & APOS; s реализацияСистемная библиотека.

1

Похожие вопросы