Hat jemand einen Regex- und / oder XML-Parser für StringBuilder oder Streams implementiert?

Ich erstelle einen Stresstest-Client, der Server hämmert und Antworten analysiert, wobei so viele Threads verwendet werden, wie der Client aufbringen kann. Ich finde mich ständig gedrosselt durch Garbage Collection (und / oder deren Fehlen), und in den meisten Fällen kommt es auf Zeichenfolgen an, die ich instanziiere, nur um sie an eine Regex- oder eine Xml-Parsing-Routine weiterzuleiten.

Wenn Sie die Regex-Klasse dekompilieren, sehen Sie dasim InnerenEs verwendet StringBuilder, um fast alles zu tun, aber Sie können nichtbestehen es ist ein Saitenbauer; Es greift hilfreich auf private Methoden zurück, bevor es verwendet wird, sodass Erweiterungsmethoden es ebenfalls nicht lösen können. Sie befinden sich in einer ähnlichen Situation, wenn Sie ein Objektdiagramm aus dem Parser in System.Xml.Linq abrufen möchten.

Dies ist kein Fall von pedantischer Überoptimierung im Voraus. Ich habe das angeschautRegex-Ersetzungen in einem StringBuilder Frage und andere. Ich habe auch meine App profiliert, um zu sehen, wo die Decken herkommen und wie sie verwendet werdenRegex.Replace() Jetzt wird in der Tat ein erheblicher Overhead in einer Methodenkette eingeführt, bei der versucht wird, einen Server mit Millionen von Anforderungen pro Stunde zu erreichen und XML-Antworten auf Fehler und eingebettete Diagnosecodes zu untersuchen. Ich habe bereits fast jede andere Ineffizienz beseitigt, die den Durchsatz drosselt, und ich habe sogar einen Großteil des Regex-Overheads eingespart, indem ich StringBuilder erweitert habe, um Platzhalter zu finden / ersetzen, wenn ich keine Erfassungsgruppen oder Rückverweise benötige. aber es scheint mir, dass jemand inzwischen ein benutzerdefiniertes StringBuilder- (oder besser Stream-) basiertes Regex- und XML-Parsing-Dienstprogramm fertiggestellt hätte.

Ok, so wütend, aber muss ich das selbst machen?

Aktualisieren: Ich habe eine Problemumgehung gefunden, durch die der maximale Speicherverbrauch von mehreren Gigabyte auf einige hundert Megabyte gesenkt wurde. Ich füge es nicht als Antwort hinzu, weil a) ich das im Allgemeinen hasse und b) ich immer noch herausfinden möchte, ob sich jemand die Zeit nimmt, StringBuilder für Regexes anzupassen (oder umgekehrt), bevor ich das tue.

In meinem Fall konnte ich XmlReader nicht verwenden, da der Stream, den ich aufnehme, in bestimmten Elementen einen ungültigen binären Inhalt enthält. Um das XML zu analysieren, muss ich diese Elemente ausleeren. Zuvor verwendete ich eine einzelne statisch kompilierte Regex-Instanz, um das Ersetzen durchzuführen, und dieser verbrauchte Speicher wie verrückt (ich versuche, ~ 300 Dokumente mit 10 KB / Sek. Zu verarbeiten). Die Änderung, die den Verbrauch drastisch senkte, war:

Ich habe den Code von diesem hinzugefügtArtikel über StringBuilder-Erweiterungen in CodeProject für das handlicheIndexOf Methode.Ich habe ein (sehr) rohes hinzugefügtWildcardReplace Methode, die erlaubtein Platzhalterzeichen (* oder?) pro AufrufIch habe die Verwendung von Regex durch eine ersetztWildcardReplace() call, um den Inhalt der beleidigenden Elemente zu leeren

Dies ist sehr unpretty und nur getestet, soweit meine eigenen Zwecke erforderlich sind; Ich hätte es eleganter und kraftvoller gemacht, aber YAGNI und all das, und ich habe es eilig. Hier ist der Code:

/// <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;
}

Antworten auf die Frage(3)

Ihre Antwort auf die Frage