¿Cómo funciona RegexOptions.Compiled?

¿Qué está sucediendo entre bastidores cuando marcas una expresión regular como una para comstackr? ¿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?

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 comstackció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)?(?http://[\w\.:]+)/questions/(?\d+)(/(\w|-)*)?(/(?\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[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemil@oneverylongemil.com" }; 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 (comstackdo frente a no comstackdo). Segundo, prueba repeticiones de coincidencias que reutilizan la misma expresión regular.

Los resultados en mi máquina (comstackdos en versión, sin depurador adjunto)

1000 uniones simples (construir Regex, Match y disponer)

 Tipo |  Plataforma |  Número trivial |  Verificación de correo electrónico simple |  Verificación de correo electrónico externo
 -------------------------------------------------- ----------------------------
 Interpretado |  x86 |  4 ms |  26 ms |  31 ms
 Interpretado |  x64 |  5 ms |  29 ms |  35 ms
 Comstackdo |  x86 |  913 ms |  3775 ms |  4487 ms
 Comstackdo |  x64 |  3300 ms |  21985 ms |  22793 ms

1.000.000 de coincidencias: reutilización del objeto Regex

 Tipo |  Plataforma |  Número trivial |  Verificación de correo electrónico simple |  Verificación de correo electrónico externo
 -------------------------------------------------- ----------------------------
 Interpretado |  x86 |  422 ms |  461 ms |  2122 ms
 Interpretado |  x64 |  436 ms |  463 ms |  2167 ms
 Comstackdo |  x86 |  279 ms |  166 ms |  1268 ms
 Comstackdo |  x64 |  281 ms |  176 ms |  1180 ms

Estos resultados muestran que las expresiones regulares comstackdas 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 comstackción de expresiones regulares.


La recomendación sería usar la versión comstackda en los casos en que

  1. 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í)
  2. 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 boostse 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 locking alrededor del cheque, por lo que el control puede disminuir el paralelismo e introducir locking 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 boostlo.

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 comstackció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.

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):

  1. interpretado

    rápido para crear sobre la marcha, lento para ejecutar

  2. comstackdo (el que parece preguntar)

    más lento de crear sobre la marcha, rápido de ejecutar (bueno para la ejecución en bucles)

  3. precomstackdo

    crear en tiempo de comstackció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.

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 comstackdas. El código de la biblioteca Regex ya no reinterpreta la misma expresión regular no comstackda cada vez.

Por lo tanto, existe una penalización de rendimiento potencialmente mayor con una expresión regular comstackda y sobre la marcha. Además de los tiempos de carga más lentos, el sistema también usa más memoria para comstackr la expresión regular a códigos de operación.

Esencialmente, el consejo actual es o no comstackr una expresión regular, o comstackrlos de antemano en un ensamble separado.

Ref: BCL Team Blog Expresión regular del rendimiento [David Gutierrez]

1) Equipo de biblioteca de clase base en expresiones regulares comstackdas

2) Codificando Horror, haciendo referencia al # 1 con algunos puntos buenos sobre las compensaciones