viernes, 2 de mayo de 2014

Receta No. 2-6 en C#: Optimizar el Uso de Expresiones Regulares en la CLR

Tabla de Contenido

0. Introducción
1. Problema
2. Solución
3. Discusión de la Solución
4. Práctica: Código Fuente C#
5. Conclusiones
6. Glosario
7. Enlaces & Literatura

0. Introducción

En la receta previa -Receta No. 2-5 en C#: Validar la Entrada de Datos con Expresiones Regulares | OrtizOL - Experiencias Construcción Software (xCSw)- aprendimos acerca del uso y la utilidad de las expresiones regulares en la validación de datos ingresados por un usuario o sistema: archivo de texto, formulario, o desde la línea de comandos. Sin embargo, en esta receta nos vamos a detener en consideraciones acerca de la optimización de expresiones regulares compiladas para ganar reducir el riesgo de ralentización o bloqueo de la aplicación cuando use repetidamente expresiones regulares complejas. ¡Manos a la obra!

1. Problema

Ya no sólo nos basta con crear validaciones sobre la data ingresa por el usuario sobre nuestra aplicación; ahora tenemos la tarea de minimizar el uso de recursos requeridos para la compilación, y el consecuente uso de la regex repetidamente desde la aplicación.

[Nota: aclaro que este caso aplica sobretodo para situaciones donde tenemos una expresión regular muy complejo (e.g., validación de direcciones de dominios de Internet (URL)) y que se usan muy frecuentemente.]

2. Solución

La clase System.Text.RegularExpressions.Regex [3] posee la siguiente firma en uno de sus constructores:

public Regex(string pattern, RegexOptions options)

El segundo parámetro de tipo RegexOptions es una enumeración que contiene un conjunto de opciones para determinar el modo de instanciar el objeto Regex; una de esas opciones nos va permitir crear compilar la expresión regular directamente Microsoft Intermediate Language (lo que conocemos como MSIL). Esto a fin de cuentas, va permitir que la regex se ejecute de forma más eficiente (consumiendo recursos de máquina (i.e., memoria de trabajo y ciclos de procesador) de modo óptimo).

En la siguiente sección vamos a discutir este asunto de rendimiento con más detalle.

3. Discusión de la Solución

En [1] nos explican que las expresiones regular creadas a partir de la instanciación de un objeto Regex, específicamente, con el constructor:

public Regex(string pattern) [5]

la expresión regular en cuestión es compilada a una representación intermedia. Esta representación no concreta con el lenguaje intermedio MSIL [4]. En resumen, podríamos  establecer la siguiente la siguiente serie de pasos comprometidos al instanciar un objeto Regex con la versión del constructor [5]:
  1. Instanciación objeto Regex con la expresión regular como argumento del constructor [5].
  2. Generación de la representación intermedia de la expresión regular durante el proceso de compilación.
Este ciclo de dos pasos (en particular el segundo), representa un consumo de recursos significativo, dado que el proceso de interpretación y conversión a la representación (MSIL) son procesos exigentes en términos de recursos máquina. Esto se dispara cuando tenemos una expresión regular compleja que es comprometida a múltiples invocaciones.

Empero, el Framework .NET nos ofrece un artefacto: la enumeración RegexOptions para atacar este consumo desmesurado de recursos. Pero antes hagamos una prueba de rendimiento.

Prueba de rendimiento: Regex(string) vs Regex(string, RegexOptions)


Resultados:

Regex(string): 2878.10, 2948.61, 3058.44, 3062.93, 2823.63
Regex(string, RegexOptions): 1965.64, 2297.61, 2328.76, 2274.72, 2307.95

Medidas estadísticas:
  • Máximo:
    • Regex(string): 3062.93
    • Regex(string, RegexOptions): 2328.76
  • Mínimo:
    • Regex(string): 2823.63
    • Regex(string, RegexOptions): 1965.4
  • Media:
    • Regex(string): 2954.34
    • Regex(string, RegexOptions): 2234.94
  • Desviación estándar:
    • Regex(string): 106.72
    • Regex(string, RegexOptions): 151.79
Como observamos en los resultados, el desempeño de la versión del constructor sobrecargado de la RegexRegex(string, RegexOptions), posee un mejor rendimiento versus Regex(string).

Continuando con la parte teórica, el hecho de aplicar RegexOptions.Compiled al constructor de Regex somete o forza a la máquina virtual -CLR- de .NET a compilar la expresión regular (en nuestro ejemplo anterior: ^((([\w]+\.[\w]+)+)|([\w]+))@(([\w]+\.)+)([A-Za-z]{1,3})$) a representación MSIL; pasando por alto la representación intermedia de la que hablamos al principio de esta sección. Vale agregar, que esta nueva representación es óptima para ser compilada por el compilador JIT de MSIL el cual está optimizado para la arquitectura del computador adyacente.

Algunas consideraciones adicionales respecto a este método de forzado de compilación:
  • A pesar de que el rendimiento en el uso de la expresión se ve notablemente mejorado, en [1] apuntan que este proceso compilación de expresiones regular introduce retardos en la compilación JIT.
  • La máquina virtual -CLR- no podrá descargar la expresión regular, sino hasta que se finalice el uso de la regex.
  • La regex permanecerá en memoria de trabajo hasta que se cierre la aplicación que la cargó.
Por otro lado, el método estático CompileToAssembly [7] facilita la compilación y creación de una expresión regular y almacenarla en un assembly por separado. De acuerdo con [1], esto nos va a permitir crear un set de expresiones regulares reutilizables por otras aplicaciones. En la Figura 1 [1] se describe para la creación de un assembly que contendrá una clase por cada regex compilada:
Proceso de creación un assembly con expresiones regulares.
Figura 1. Proceso de creación un assembly con expresiones regulares [1].

4. Práctica: Código Fuente C#

Esta parte de la receta va a ser muy interesante: vamos a crear un assembly con un dos expresiones regulares: .


Compilación:


  1. csc /target:exe EnsambladorRegex.cs

Ejecución:


  1. .\EnsambladorRegex.exe

Resultado:
Creación de assembly con expresiones regulares
Figura 2. Creación de assembly con expresiones regulares.

Conclusiones

Esta ha resultado ser una receta interesante, pues no sólo nos hemos fijado en un aspecto funcional de las expresiones regular, sino que hemos tenido la posibilidad de mejorar su desempeño a través de los artefactos definidos en el namespace System.Text.RegularExpressions, en particular el uso del constructor sobrecargado de Regex: Regex(string, RegexOptions). Además, al final vimos cómo crear un conjunto de expresiones regulares (RegexPIN, y RegexTarjetaCredito) para ser reutilizados en múltiples aplicaciones y mejorar el desempeño y modularidad de la aplicación al separarlo en un assembly externo.

Glosario


  • Assembly
  • Desempeño
  • Expresión regular
  • Optimización
  • Regex

Enlaces & Literatura

[1]: Visual C# 2010 Recipes by Allen Jones and Adam Freeman. Copyright 2010 Allen Jones and Adam Freeman, 978-1-4302-2525-6.
[2]: Receta No. 2-5 en C#: Validar la Entrada de Datos con Expresiones Regulares | OrtizOL - Experiencias Construcción Software (xCSw) - http://ortizol.blogspot.com/2014/05/receta-no-2-5-en-c-validar-la-entrada.html
[3]: Regex Class (System.Text.RegularExpressions) - http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex%28v=vs.110%29.aspx
[4]: Compiling to MSIL - http://msdn.microsoft.com/en-us/library/c5tkafs1%28v=vs.90%29.aspx
[5]: Regex Constructor (String) (System.Text.RegularExpressions) - http://msdn.microsoft.com/en-us/library/43122zyz(v=vs.110).aspx
[6]: Just-in-time compilation, the free encyclopedia - https://en.wikipedia.org/wiki/Just-in-time_compilation
[7]: Regex.CompileToAssembly Method (System.Text.RegularExpressions) - http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.compiletoassembly%28v=vs.110%29.aspx


M

No hay comentarios:

Publicar un comentario

Envíe sus comentarios, dudas, sugerencias, críticas. Gracias.