2012-07-18 14 views
9

Sto costruendo un client di testing dello stress che martella i server e analizza le risposte utilizzando tutti i thread che il client può radunare. Mi trovo costantemente strozzato dalla garbage collection (e/o dalla sua mancanza), e nella maggior parte dei casi, si tratta di stringhe che sto istanziando solo per passarle a una routine di analisi Regex o Xml.Qualcuno ha implementato un parser Regex e/o Xml su StringBuilders o Stream?

Se decompilare la classe Regex, vedrete che internamente, utilizza StringBuilders di fare quasi tutto, ma non puoi passaggio è un costruttore d'archi; si immerge utilmente in metodi privati ​​prima di iniziare a usarli, quindi neanche i metodi di estensione risolveranno. Ci si trova in una situazione simile se si desidera ottenere un oggetto grafico dal parser in System.Xml.Linq.

Questo non è un caso di eccessiva ottimizzazione pedante in anticipo. Ho esaminato la domanda Regex replacements inside a StringBuilder e altri. Ho anche profilato la mia app per vedere da dove provengono i controsoffitti, e l'utilizzo di Regex.Replace() ora sta davvero introducendo un overhead significativo in una catena di metodi in cui sto cercando di colpire un server con milioni di richieste all'ora ed esaminare le risposte XML per gli errori e codici diagnostici integrati. Mi sono già sbarazzato di ogni altra inefficienza che sta riducendo il throughput, e ho anche tagliato un sacco di overhead del Regex estendendo StringBuilder per fare find/replace con caratteri jolly quando non ho bisogno di gruppi di cattura o backreferences, ma a me sembra che qualcuno avrebbe finito con il wrapping di un personalizzatore di stringhe personalizzato per Regex e Xml (o meglio ancora, Stream).

Ok, così inveire, ma dovrò farlo da solo?

Aggiornamento: Ho trovato una soluzione alternativa che ha ridotto il consumo di memoria di picco da più gigabyte a poche centinaia di megabyte, quindi lo posterò di seguito. Non lo aggiungo come risposta perché a) Generalmente odio farlo, e b) Voglio ancora scoprire se qualcuno impiega il tempo per personalizzare StringBuilder per fare Regexes (o vice-versa) prima di me.

Nel mio caso, non ho potuto utilizzare XmlReader perché lo stream che sto ingestando contiene alcuni contenuti binari non validi in alcuni elementi. Per analizzare l'XML, devo svuotare quegli elementi. In precedenza stavo usando una singola istanza Regex compilata statica per fare la sostituzione, e questo consumava memoria come un matto (sto cercando di elaborare ~ 300 10 KB doc/sec). Il cambiamento che riduce drasticamente il consumo era:

  1. ho aggiunto il codice da questa StringBuilder Extensions article on CodeProject per il metodo pratico IndexOf.
  2. ho aggiunto un (molto) grezzo WildcardReplace metodo che permette uno carattere jolly (* o?) Per invocazione
  3. ho sostituito l'utilizzo Regex con una chiamata WildcardReplace() svuotare il contenuto degli elementi illegittimi

Questo è molto imprevedibile e testato solo per i miei scopi richiesti; L'avrei reso più elegante e potente, ma YAGNI e tutto il resto, e ho fretta. Ecco il codice:

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

È possibile nel vostro scenario salvare i dati grezzi e analizzarli in seguito? Ho visto una sorta di analisi che ha preso questo approccio ... – Andre

+0

@Andre, sì, questo è probabilmente un buon suggerimento, l'ho appena evitato finora a causa di tutta la logica che avrei dovuto svelare. La strategia attuale consiste nell'analizzare in modo asincrono tutto, ottenere il grafico degli oggetti richiesto dalla risposta e lanciarlo in MongoDB per un'analisi più approfondita in seguito. Quindi suppongo che se non intraprendo la decompilazione di tutto ciò che Regex dipende e personalizzando tutto ciò che è necessario per invocare un .Replace(), questa è la prossima opzione migliore. Se nessuno tossisce una soluzione pre-rollata, credo che dovrò prendere quella decisione. –

+0

Due ottimizzazioni che non hai menzionato utilizzano 'RegexOptions.Compiled' per le tue espressioni regolari e l'uso del garbage collector del server. Hai fatto entrambi? –

risposta