Estoy construyendo un cliente de pruebas de estrés que martillea los servidores y analiza las respuestas usando tantos hilos como el cliente pueda reunir. Constantemente me encuentro acelerado por la recolección de basura (y/o la falta de ella), y en la mayoría de los casos, se trata de cadenas que estoy instanciando solo para pasarlas a una rutina de análisis Rex o Xml.¿Alguien ha implementado un analizador Regex y/o Xml alrededor de StringBuilders o Streams?
Si descompilar la clase Regex, podrás ver que internamente, se utiliza StringBuilders a hacer casi todo, pero no puedes pase es un constructor de cadena; de manera útil se sumerge en métodos privados antes de comenzar a usarlos, por lo que los métodos de extensión tampoco lo van a resolver. Se encuentra en una situación similar si desea obtener un gráfico de objetos del analizador en System.Xml.Linq.
Este no es un caso de exceso de optimización pedante en el avance. He visto la pregunta Regex replacements inside a StringBuilder y otros. También realicé un perfil de mi aplicación para ver de dónde provienen los límites máximos, y usar Regex.Replace()
ahora está introduciendo gastos indirectos significativos en una cadena de métodos en la que trato de llegar a un servidor con millones de solicitudes por hora y examinar las respuestas XML para detectar errores y códigos de diagnóstico integrados. Ya me he deshecho de casi todas las otras ineficiencias que están acelerando el rendimiento, e incluso he recortado un montón de Regex extendiendo StringBuilder para buscar/reemplazar comodines cuando no necesito grupos de captura o referencias retrospectivas, pero me parece que alguien ya habría completado una utilidad de análisis Regex y Xml basada en StringBuilder (o mejor aún, Stream).
Ok, así que despotrican, pero ¿tendré que hacer esto yo mismo?
Actualización: Encontré una solución que redujo el consumo máximo de memoria de varios gigabytes a unos pocos cientos de megas, por lo que lo publico a continuación. No lo estoy agregando como respuesta porque a) En general, odio hacer eso, yb) Todavía quiero saber si alguien se toma el tiempo de personalizar StringBuilder para hacer Regex (o viceversa) antes que yo.
En mi caso, no pude usar XmlReader porque la transmisión que estoy ingiriendo contiene contenido binario no válido en ciertos elementos. Para analizar el XML, tengo que vaciar esos elementos. Anteriormente estaba usando una sola instancia de Regex compilada estática para hacer el reemplazo, y esta memoria consumida como loca (estoy tratando de procesar ~ 300 10KB docs/seg). El cambio que reduce drásticamente el consumo fue:
- he añadido el código de este StringBuilder Extensions article on CodeProject para el método práctico
IndexOf
. - he añadido un (muy) de crudo
WildcardReplace
método que permite uno carácter comodín (* o?) Por la invocación - He sustituido el uso de expresiones regulares con una llamada
WildcardReplace()
para vaciar el contenido de los elementos ofensivos
Esto es muy poco atractivo y probado solo en la medida en que lo requiera mi propio propósito; Lo habría hecho más elegante y poderoso, pero YAGNI y todo eso, y estoy apurado. Aquí está el código:
/// <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;
}
¿Es viable en su escenario guardar los datos sin procesar y analizarlos más tarde? He visto algún tipo de análisis que tomó este enfoque ... – Andre
@ André, sí, esa es probablemente una buena sugerencia, la he evitado hasta ahora debido a toda la lógica que tendría que desentrañar. La estrategia actual es analizar asincrónicamente todo, obtener el gráfico de objeto requerido de la respuesta y lanzarlo en MongoDB para un análisis más profundo más adelante. Así que supongo que si no emprender la descompilación de todo lo que Regex depende y personalizar todo lo necesario para invocar un .Replace(), esa es la siguiente mejor opción. Si nadie tose una solución pre-rodada, creo que tendré que tomar esa decisión. –
Dos optimizaciones que no mencionaste están usando 'RegexOptions.Compiled' para tus expresiones regulares, y usando el recolector de basura del servidor. ¿Has hecho ambas cosas? –