Pregunta

Esta es solo una pregunta para satisfacer mi curiosidad. Pero para mí es interesante.

Escribí este pequeño punto de referencia simple. Llama a 3 variantes de ejecución de Regexp en un orden aleatorio unas pocas miles de veces:

Básicamente, uso el mismo patrón pero de diferentes maneras.

  1. Su forma habitual sin ningún RegexOptions . Comenzando con .NET 2.0, estos no se almacenan en caché. Pero debe estar "en caché" porque se mantiene en un ámbito bastante global y no se restablece.

  2. Con RegexOptions.Compiled

  3. Con una llamada al Regex.Match estático (patrón, entrada) que se almacena en caché en .NET 2.0

Aquí está el código:

static List<string> Strings = new List<string>();        
static string pattern = ".*_([0-9]+)\\.([^\\.])
    Not compiled and not automatically cached:
    Total milliseconds: 6185,2704
    Adjusted milliseconds: 6185,2704

    Compiled and not automatically cached:
    Total milliseconds: 2562,2519
    Adjusted milliseconds: 2551,56949184038

    Not compiled and automatically cached:
    Total milliseconds: 2378,823
    Adjusted milliseconds: 2336,3187176891
quot;; static Regex Rex = new Regex(pattern); static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled); static Random Rand = new Random(123); static Stopwatch S1 = new Stopwatch(); static Stopwatch S2 = new Stopwatch(); static Stopwatch S3 = new Stopwatch(); static void Main() { int k = 0; int c = 0; int c1 = 0; int c2 = 0; int c3 = 0; for (int i = 0; i < 50; i++) { Strings.Add("file_" + Rand.Next().ToString() + ".ext"); } int m = 10000; for (int j = 0; j < m; j++) { c = Rand.Next(1, 4); if (c == 1) { c1++; k = 0; S1.Start(); foreach (var item in Strings) { var m1 = Rex.Match(item); if (m1.Success) { k++; }; } S1.Stop(); } else if (c == 2) { c2++; k = 0; S2.Start(); foreach (var item in Strings) { var m2 = RexCompiled.Match(item); if (m2.Success) { k++; }; } S2.Stop(); } else if (c == 3) { c3++; k = 0; S3.Start(); foreach (var item in Strings) { var m3 = Regex.Match(item, pattern); if (m3.Success) { k++; }; } S3.Stop(); } } Console.WriteLine("c: {0}", c1); Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("c: {0}", c2); Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString()); Console.WriteLine("c: {0}", c3); Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString()); Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString()); }

Cada vez que lo llamo, el resultado está en la línea de:

    Not compiled and not automatically cached:
    Total milliseconds: 6456,5711
    Adjusted milliseconds: 6456,5711

    Compiled and not automatically cached:
    Total milliseconds: 2668,9028
    Adjusted milliseconds: 2657,77574842168

    Not compiled and automatically cached:
    Total milliseconds: 6637,5472
    Adjusted milliseconds: 6518,94897724836

Entonces ahí lo tienes. No mucho, pero alrededor del 7-8% de diferencia.

No es el único misterio. No puedo explicar por qué la primera forma sería mucho más lenta porque nunca se vuelve a evaluar, sino que se mantiene en una variable estática global.

Por cierto, esto está en .Net 3.5 y Mono 2.2 que se comportan exactamente igual. En Windows.

Entonces, ¿alguna idea, por qué la variante compilada se quedaría atrás?

EDITAR1:

Después de corregir el código, los resultados ahora se ven así:

<*>

Lo cual también hace obsoletas todas las otras preguntas también.

Gracias por las respuestas.

¿Fue útil?

Solución

En la versión Regex.Match está buscando la entrada en el patrón. Intente intercambiar los parámetros.

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct

Otros consejos

Me di cuenta similar comportamiento. También me preguntaba por qué la versión compilada sería más lenta, pero noté que por encima de un cierto número de llamadas, la versión compilada es más rápida. Así que busqué Reflector un poco, y noté que para una Regex compilada, todavía hay una pequeña configuración que se realiza en la primera llamada (específicamente, crear una instancia del RegexRunner objeto).

En mi prueba, descubrí que si movía tanto el constructor como una llamada inicial desechable a la expresión regular fuera del inicio del temporizador, la expresión regular compilada ganaba sin importar cuántas iteraciones ejecutara.


Por cierto, el almacenamiento en caché que el framework está haciendo cuando usa métodos estáticos Regex es una optimización que solo es necesaria cuando se usan métodos estáticos Regex . Esto se debe a que cada llamada a un método estático Regex crea un nuevo objeto Regex . En el constructor de la clase Regex , debe analizar el patrón. El almacenamiento en caché permite que las llamadas posteriores de los métodos estáticos Regex reutilicen el RegexTree analizado desde la primera llamada, evitando así el paso de análisis.

Cuando utiliza métodos de instancia en un solo objeto Regex , esto no es un problema. El análisis todavía se realiza solo una vez (cuando crea el objeto). Además, puede evitar ejecutar todos los demás códigos en el constructor, así como la asignación de almacenamiento dinámico (y la posterior recolección de basura).

Martin Brown notó que usted invirtió los argumentos en su llamada estática Regex (buena captura, Martin). Creo que encontrará que si arregla eso, la expresión regular de la instancia (no compilada) superará las llamadas estáticas cada vez. También debe encontrar que, dados mis hallazgos anteriores, la instancia compilada también superará a la no compilada.

PERO : Deberías leer Publicación de Jeff Atwood sobre expresiones regulares compiladas antes de que apliques ciegamente esa opción a cada expresión regular que crees.

Si hace coincidir constantemente la misma cadena con el mismo patrón, eso puede explicar por qué una versión en caché es ligeramente más rápida que una versión compilada.

Esto es de documentación;

https://msdn.microsoft.com /en-us/library/gg578045(v=vs.110).aspx

  

cuando se llama a un método de expresión regular estática y el   la expresión no se puede encontrar en el caché, el motor de expresión regular   Convierte la expresión regular en un conjunto de códigos de operación y almacena   ellos en el caché . Luego convierte estos códigos de operación a MSIL   que el compilador JIT puede ejecutarlos. Interpretado regularmente   las expresiones reducen el tiempo de inicio a costa de un tiempo de ejecución más lento .   Debido a esto, se utilizan mejor cuando la expresión regular es   utilizado en un pequeño número de llamadas a métodos , o si el número exacto de   las llamadas a los métodos de expresión regular son desconocidas pero se espera que sean   pequeña. A medida que aumenta el número de llamadas a métodos, la ganancia de rendimiento   el tiempo de inicio reducido se ve superado por la ejecución más lenta   velocidad.

     

En contraste con las expresiones regulares interpretadas, compiló regular   las expresiones aumentan el tiempo de inicio pero se ejecutan individualmente   métodos de coincidencia de patrones más rápidos . Como resultado, el beneficio de rendimiento   que resulta de compilar la expresión regular aumenta en   proporción al número de métodos de expresión regular llamados.


  

Para resumir, le recomendamos que use expresiones regulares interpretadas cuando llame a métodos de expresión regular con un   expresión regular con poca frecuencia.

     

Debe utilizar expresiones regulares compiladas cuando llame a regular   métodos de expresión con una expresión regular específica relativamente   con frecuencia.


¿Cómo detectarlo?

  

El umbral exacto en el que las velocidades de ejecución más lentas de   Las expresiones regulares interpretadas superan las ganancias de su reducido   tiempo de inicio, o el umbral en el que los tiempos de inicio más lentos de   las expresiones regulares compiladas superan las ganancias de su velocidad más rápida   velocidades de ejecución, es difícil de determinar. Depende de una variedad   de factores, incluida la complejidad de la expresión regular y la   datos específicos que procesa. Para determinar si interpretado o   las expresiones regulares compiladas ofrecen el mejor rendimiento para su   escenario de aplicación particular, puede usar la clase Cronómetro para   compare sus tiempos de ejecución .


Expresiones regulares compiladas:

  

Recomendamos que compile expresiones regulares en un ensamblado en   las siguientes situaciones:

     
      
  1. Si usted es un desarrollador de componentes que quiere   para crear una biblioteca de expresiones regulares reutilizables.
  2.   
  3. Si esperas   los métodos de coincidencia de patrones de su expresión regular se denominarán   número indeterminado de veces, desde una o dos veces hasta   miles o decenas de miles de veces. A diferencia de compilado o   expresiones regulares interpretadas, expresiones regulares que se compilan   separar conjuntos ofrecen un rendimiento consistente independientemente   del número de llamadas a métodos.
  4.   
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top