.net - online - regex operators
¿Cómo funciona RegexOptions.Compiled? (4)
¿Qué está sucediendo entre bastidores cuando marcas una expresión regular como una para compilar? ¿Cómo se compara / es diferente de una expresión regular en caché?
Usando esta información, ¿cómo se determina cuándo el costo del cálculo es insignificante en comparación con el aumento del rendimiento?
Cabe señalar que el rendimiento de las expresiones regulares desde .NET 2.0 se ha mejorado con un caché MRU de expresiones regulares no compiladas. El código de la biblioteca Regex ya no reinterpreta la misma expresión regular no compilada cada vez.
Por lo tanto, existe una penalización de rendimiento potencialmente mayor con una expresión regular compilada y sobre la marcha. Además de los tiempos de carga más lentos, el sistema también usa más memoria para compilar la expresión regular a códigos de operación.
Esencialmente, el consejo actual es o no compilar una expresión regular, o compilarlos de antemano en un ensamble separado.
Ref: BCL Team Blog Expresión regular del rendimiento [David Gutierrez]
Esta entrada en el Blog del equipo BCL ofrece una buena descripción general: " Rendimiento de expresión regular ".
En resumen, hay tres tipos de expresiones regulares (cada una ejecutando más rápido que la anterior):
interpretado
rápido para crear sobre la marcha, lento para ejecutar
compilado (el que parece preguntar)
más lento de crear sobre la marcha, rápido de ejecutar (bueno para la ejecución en bucles)
precompilado
crear en tiempo de compilación de su aplicación (sin penalización de creación en tiempo de ejecución), rápido para ejecutar
Por lo tanto, si tiene la intención de ejecutar la expresión regular solo una vez, o en una sección que no es de rendimiento crítico de su aplicación (es decir, la validación de entrada del usuario), está bien con la opción 1.
Si tiene la intención de ejecutar la expresión regular en un bucle (es decir, análisis de línea por línea del archivo), debe ir con la opción 2.
Si tiene muchas expresiones regulares que nunca cambiarán para su aplicación y se usan intensamente, puede ir con la opción 3.
RegexOptions.Compiled
instruye al motor de expresiones regulares para que compile la expresión de expresión regular en IL usando la generación de código ligero ( LCG ). Esta compilación ocurre durante la construcción del objeto y lo ralentiza mucho . A su vez, las coincidencias que usan la expresión regular son más rápidas.
Si no especifica este indicador, su expresión regular se considera "interpretada".
Toma este ejemplo:
public static void TimeAction(string description, int times, Action func)
{
// warmup
func();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < times; i++)
{
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
static void Main(string[] args)
{
var simple = "^//d+$";
var medium = @"^((to|from)/W)?(?<url>http://[/w/.:]+)/questions/(?<questionId>/d+)(/(/w|-)*)?(/(?<answerId>/d+))?";
var complex = @"^(([^<>()[/]//.,;:/s@""]+"
+ @"(/.[^<>()[/]//.,;:/s@""]+)*)|("".+""))@"
+ @"((/[[0-9]{1,3}/.[0-9]{1,3}/.[0-9]{1,3}"
+ @"/.[0-9]{1,3}/])|(([a-zA-Z/-0-9]+/.)+"
+ @"[a-zA-Z]{2,}))$";
string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
string[] emails = new string[] { "[email protected]", "sss@s", "[email protected]", "[email protected]" };
foreach (var item in new[] {
new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
new {Pattern = medium, Matches = emails, Name = "Simple email match"},
new {Pattern = complex, Matches = emails, Name = "Complex email match"}
})
{
int i = 0;
Regex regex;
TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
i = 0;
TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern, RegexOptions.Compiled);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern);
i = 0;
TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern, RegexOptions.Compiled);
i = 0;
TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
}
}
Realiza 4 pruebas en 3 expresiones regulares diferentes. Primero prueba un único partido único (compilado frente a no compilado). Segundo, prueba repeticiones de coincidencias que reutilizan la misma expresión regular.
Los resultados en mi máquina (compilados en versión, sin depurador adjunto)
1000 uniones simples (construir Regex, Match y disponer)
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 4 ms | 26 ms | 31 ms Interpreted | x64 | 5 ms | 29 ms | 35 ms Compiled | x86 | 913 ms | 3775 ms | 4487 ms Compiled | x64 | 3300 ms | 21985 ms | 22793 ms
1.000.000 de coincidencias: reutilización del objeto Regex
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 422 ms | 461 ms | 2122 ms Interpreted | x64 | 436 ms | 463 ms | 2167 ms Compiled | x86 | 279 ms | 166 ms | 1268 ms Compiled | x64 | 281 ms | 176 ms | 1180 ms
Estos resultados muestran que las expresiones regulares compiladas pueden ser hasta un 60% más rápidas para los casos en los que reutiliza el objeto Regex
. Sin embargo, en algunos casos puede ser más de 3 órdenes de magnitud más lenta de construir.
También muestra que la versión x64 de .NET puede ser 5 a 6 veces más lenta cuando se trata de la compilación de expresiones regulares.
La recomendación sería usar la versión compilada en los casos en que
- No le importa el costo de inicialización de objetos y necesita un aumento de rendimiento adicional. (tenga en cuenta que estamos hablando de fracciones de milisegundos aquí)
- Le importa un poco el costo de inicialización, pero está reutilizando el objeto Regex tantas veces que lo compensará durante el ciclo de vida de su aplicación.
Llave en el trabajo, el caché Regex
El motor de expresiones regulares contiene un caché LRU que contiene las últimas 15 expresiones regulares que se probaron utilizando los métodos estáticos en la clase Regex
.
Por ejemplo: Regex.Replace
, Regex.Replace
, etc. todos usan el caché Regex.
El tamaño de la memoria caché puede aumentarse configurando Regex.CacheSize
. Acepta cambios de tamaño en cualquier momento durante el ciclo de vida de su aplicación.
Las nuevas expresiones regulares solo son almacenadas en caché por los helpers estáticos en la clase Regex. Si construye sus objetos, la caché se verifica (para reutilización y se golpea), sin embargo, la expresión regular que construye no se agrega al caché .
Este caché es un caché de LRU trivial , se implementa mediante una simple lista de doble enlace. Si lo aumenta a 5000 y utiliza 5000 llamadas diferentes en los asistentes estáticos, cada construcción de expresión regular rastreará las 5000 entradas para ver si se ha almacenado previamente en caché. Hay un bloqueo alrededor del cheque, por lo que el control puede disminuir el paralelismo e introducir bloqueo de hilo.
El número es bastante bajo para protegerse de casos como este, aunque en algunos casos puede que no tenga más remedio que aumentarlo.
Mi gran recomendación es que nunca pase la opción RegexOptions.Compiled
a un helper estático.
Por ejemplo:
// WARNING: bad code
Regex.IsMatch("10000", @"//d+", RegexOptions.Compiled)
La razón es que corres el riesgo de perder un error en el caché de LRU, lo que provocará una compilación súper costosa . Además, no tiene idea de qué hacen las bibliotecas de las que depende, por lo que tiene poca capacidad para controlar o predecir el mejor tamaño posible de la memoria caché.
Ver también: blog del equipo BCL
Nota : esto es relevante para .NET 2.0 y .NET 4.0. Hay algunos cambios esperados en 4.5 que pueden hacer que esto se revise.