.net - regular - ¿Por qué el rendimiento RegEx compilado es más lento que el RegEx intrepretado?
probar expresiones regulares (4)
El problema con este punto de referencia es que los Regex compilados tienen la sobrecarga de crear un conjunto completamente nuevo y cargarlo en el dominio de aplicación.
El escenario para el que se diseñó Regex (creo - no los diseñé) es que se ejecuten decenas de Regex millones de veces, y no miles de Regex se ejecutan miles de veces. Si no va a ejecutar un Regex en el reino de un millón de veces, es probable que ni siquiera compense el tiempo para compilarlo con JIT.
Me encuentro con este artículo:
Rendimiento: expresiones regulares compiladas versus interpretadas , modifiqué el código de muestra para compilar 1000 Regex y luego ejecutarlo cada 500 veces para aprovechar la precompilación, sin embargo, incluso en ese caso, los RegExes interpretados se ejecutan 4 veces más rápido.
Esto significa que la opción La gran diferencia se debió a JIT, después de resolver el regex compilado de JIT en el siguiente código, todavía funciona un poco lento y no tiene sentido para mí, pero @Jim en las respuestas proporcionó una versión mucho más limpia que funciona como se esperaba . RegexOptions.Compiled
es completamente inútil, incluso peor, ¡es más lento!
¿Alguien puede explicar por qué este es el caso?
Código, tomado y modificado de la publicación del blog:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace RegExTester
{
class Program
{
static void Main(string[] args)
{
DateTime startTime = DateTime.Now;
for (int i = 0; i < 1000; i++)
{
CheckForMatches("some random text with email address, [email protected]" + i.ToString());
}
double msTaken = DateTime.Now.Subtract(startTime).TotalMilliseconds;
Console.WriteLine("Full Run: " + msTaken);
startTime = DateTime.Now;
for (int i = 0; i < 1000; i++)
{
CheckForMatches("some random text with email address, [email protected]" + i.ToString());
}
msTaken = DateTime.Now.Subtract(startTime).TotalMilliseconds;
Console.WriteLine("Full Run: " + msTaken);
Console.ReadLine();
}
private static List<Regex> _expressions;
private static object _SyncRoot = new object();
private static List<Regex> GetExpressions()
{
if (_expressions != null)
return _expressions;
lock (_SyncRoot)
{
if (_expressions == null)
{
DateTime startTime = DateTime.Now;
List<Regex> tempExpressions = new List<Regex>();
string regExPattern =
@"^[a-zA-Z0-9]+[a-zA-Z0-9._%-]*@{0}$";
for (int i = 0; i < 2000; i++)
{
tempExpressions.Add(new Regex(
string.Format(regExPattern,
Regex.Escape("domain" + i.ToString() + "." +
(i % 3 == 0 ? ".com" : ".net"))),
RegexOptions.IgnoreCase));// | RegexOptions.Compiled
}
_expressions = new List<Regex>(tempExpressions);
DateTime endTime = DateTime.Now;
double msTaken = endTime.Subtract(startTime).TotalMilliseconds;
Console.WriteLine("Init:" + msTaken);
}
}
return _expressions;
}
static List<Regex> expressions = GetExpressions();
private static void CheckForMatches(string text)
{
DateTime startTime = DateTime.Now;
foreach (Regex e in expressions)
{
bool isMatch = e.IsMatch(text);
}
DateTime endTime = DateTime.Now;
//double msTaken = endTime.Subtract(startTime).TotalMilliseconds;
//Console.WriteLine("Run: " + msTaken);
}
}
}
Es casi seguro que esto indica que su código de referencia está escrito incorrectamente en comparación con las expresiones regulares compiladas, siendo más lento que las interpretadas. Hay una gran cantidad de trabajo que se dedicó a hacer un compilador de expresiones regulares.
Ahora que tenemos el código podemos ver algunas cosas específicas que deben actualizarse.
- Este código no tiene en cuenta los costos JIT del método. Debe ejecutar el código una vez para eliminar los costos JIT y luego ejecutarlo nuevamente y medir
- ¿Por qué se utiliza el
lock
en absoluto? Es completamente innecesario - Los puntos de referencia deben usar
StopWatch
noDateTime
- Para obtener una buena comparación entre compilado y no compilado, debe probar el rendimiento de un solo
Regex
compilado y un soloRegex
no compilado que coincida N veces. No N de cada coincidencia como máximo una vez por expresión regular.
Las expresiones regulares compiladas coinciden más rápido cuando se usan según lo previsto. Como otros han señalado, la idea es compilarlos una vez y usarlos muchas veces. El tiempo de construcción e inicialización se amortized a lo largo de esas muchas corridas.
Creé una prueba mucho más simple que le mostrará que las expresiones regulares compiladas son, sin duda, más rápidas que las no compiladas.
const int NumIterations = 1000;
const string TestString = "some random text with email address, [email protected]";
const string Pattern = "^[a-zA-Z0-9]+[a-zA-Z0-9._%-]*@domain0//.//.com$";
private static Regex NormalRegex = new Regex(Pattern, RegexOptions.IgnoreCase);
private static Regex CompiledRegex = new Regex(Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
private static Regex DummyRegex = new Regex("^.$");
static void Main(string[] args)
{
var DoTest = new Action<string, Regex, int>((s, r, count) =>
{
Console.Write("Testing {0} ... ", s);
Stopwatch sw = Stopwatch.StartNew();
for (int i = 0; i < count; ++i)
{
bool isMatch = r.IsMatch(TestString + i.ToString());
}
sw.Stop();
Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);
});
// Make sure that DoTest is JITed
DoTest("Dummy", DummyRegex, 1);
DoTest("Normal first time", NormalRegex, 1);
DoTest("Normal Regex", NormalRegex, NumIterations);
DoTest("Compiled first time", CompiledRegex, 1);
DoTest("Compiled", CompiledRegex, NumIterations);
Console.WriteLine();
Console.Write("Done. Press Enter:");
Console.ReadLine();
}
Establecer NumIterations
a 500 me da esto:
Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 1 ms
Testing Compiled first time ... 13 ms
Testing Compiled ... 1 ms
Con 5 millones de iteraciones, obtengo:
Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 17,232 ms
Testing Compiled first time ... 17 ms
Testing Compiled ... 15,299 ms
Aquí puede ver que la expresión regular compilada es al menos un 10% más rápida que la versión no compilada.
Es interesante observar que si elimina RegexOptions.IgnoreCase
de su expresión regular, los resultados de 5 millones de iteraciones son aún más sorprendentes:
Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 12,869 ms
Testing Compiled first time ... 14 ms
Testing Compiled ... 8,332 ms
Aquí, la expresión regular compilada es un 35% más rápida que la expresión regular no compilada.
En mi opinión, la publicación del blog que usted menciona es simplemente una prueba defectuosa.
http://www.codinghorror.com/blog/2005/03/to-compile-or-not-to-compile.html
Compilado ayuda solo si lo crea una vez y lo reutiliza varias veces. Si está creando una expresión regular compilada en el bucle for, obviamente tendrá un peor desempeño. ¿Nos puede mostrar su código de muestra?