2009-04-29 16 views
5

Estoy generando expresiones regulares de forma dinámica ejecutando algunas estructuras xml y construyendo la declaración mientras disparo a través de sus tipos de nodos. Estoy usando esta expresión regular como parte de un tipo de diseño que definí. Luego analizo a través de un archivo de texto que tiene un Id al principio de cada línea. Esta identificación me señala un diseño específico. Luego trato de hacer coincidir los datos en esa fila con su expresión regular.¡Las expresiones regulares dinámicamente construidas se están ejecutando extremadamente lento!

Suena bien y dandy ¿no? El único problema es que las cadenas coincidentes son extremadamente lentas. Los tengo configurados como compilados para intentar acelerar un poco las cosas, pero fue en vano. Lo desconcertante es que estas expresiones no son tan complejas. No soy de ninguna manera un gurú de RegEx, pero sé un cantidad decente sobre ellos para que las cosas funcionen bien.

Aquí está el código que genera las expresiones ...

StringBuilder sb = new StringBuilder(); 
//get layout id and memberkey in there... 
sb.Append(@"^([0-9]+)[ \t]{1,2}([0-9]+)"); 
foreach (ColumnDef c in columns) 
{ 
    sb.Append(@"[ \t]{1,2}"); 
    switch (c.Variable.PrimType) 
    { 
     case PrimitiveType.BIT: 
      sb.Append("(0|1)"); 
      break; 
     case PrimitiveType.DATE: 
      sb.Append(@"([0-9]{2}/[0-9]{2}/[0-9]{4})"); 
      break; 
     case PrimitiveType.FLOAT: 
      sb.Append(@"([-+]?[0-9]*\.?[0-9]+)"); 
      break; 
     case PrimitiveType.INTEGER: 
      sb.Append(@"([0-9]+)"); 
      break; 
     case PrimitiveType.STRING: 
      sb.Append(@"([a-zA-Z0-9]*)"); 
      break; 
    } 
} 
sb.Append("$"); 
_pattern = new Regex(sb.ToString(), RegexOptions.Compiled); 

La parte lenta real ...

public System.Text.RegularExpressions.Match Match(string input) 
{ 
    if (input == null) 
     throw new ArgumentNullException("input"); 

    return _pattern.Match(input); 
} 

A "_pattern" típica puede tener unos 40-50 columnas. Guardaré de pegar todo el patrón. Intento agrupar cada caso para poder enumerar cada caso en el objeto Match más adelante.

¿Alguna sugerencia o modificación que podría ayudar drásticamente? ¿O esto es lento para ser esperado?

EDITAR POR CLARIDAD: Lo siento, no creo que haya sido lo suficientemente claro la primera vez.

Utilizo un archivo xml para generar expresiones regulares para un diseño específico. Luego reviso un archivo para importar datos. Necesito asegurarme de que cada línea en el archivo coincida con el patrón que dice que se supone que es. Por lo tanto, los patrones se pueden comparar varias veces, miles posibles.

+0

Puede probar [01] para BIT. – Dave

+0

¿Está tratando de hacer coincidir CSV con esta expresión regular? – Gumbo

+0

Su salida delimitada por tabulaciones desde SAS –

Respuesta

8

Está analizando un archivo CSV de 50 columnas (que usa pestañas) con expresiones regulares?

Simplemente debe eliminar las pestañas duplicadas, luego divida el texto en \ t. Ahora tiene todas sus columnas en una matriz.Puede usar su colección de objetos ColumnDef para decirle qué es cada columna.

Editar: Una vez que haya dividido las cosas, puede usar opcionalmente regex para verificar cada valor, esto debería ser mucho más rápido que usar la única expresión regular gigante.

Edit2: También obtiene una ventaja adicional al saber exactamente qué columnas están mal formateadas y puede producir un error como "Error de Sytax en la columna 30 en la línea 12, formato de fecha esperado".

+0

Empiezo a pensar que esto será mucho más rápido y más simple también. –

+0

Esta es probablemente la mejor solución presentada hasta ahora.Utilizo docenas de expresiones regulares complejas diariamente (procesando texto de publicación y XML). IME, una vez que sus expresiones regulares alcanzan una cierta "masa crítica" de complejidad, el rendimiento baja por los tubos. Dividir este problema en trozos más pequeños será su camino alrededor de este cuello de botella. – patjbs

1

Tener un potencial de 50 grupos de coincidencias en una sola expresión por defecto va a ser un poco lento. Haría algunas cosas para ver si puedes precisar el retroceso en el rendimiento.

  1. Comience por probar una comparación codificada, vs dinámica y vea si tiene algún beneficio de rendimiento.
  2. Observe sus requisitos y vea si hay alguna forma de reducir el número de agrupaciones que necesita evaluar
  3. Use una herramienta de perfilador si es necesario, como Ants Profiler para ver la ubicación de la ralentización.
2

Bien. Construir el patrón usando un StringBuilder ahorrará algunos ciclos, en comparación con concatenarlos.

Una optimización en esto que es drástica (se puede medir visiblemente) lo más probable es que lo haga mediante algún otro método.

Las expresiones regulares son lentas ... poderosas pero lentas. Analizar a través de un archivo de texto y luego comparar el uso de expresiones regulares para recuperar los bits correctos de datos no será muy rápido (depende de la computadora host y del tamaño del archivo de texto).

Quizás el almacenamiento de los datos en algún otro método en lugar de un archivo de texto grande sería más eficiente de analizar (¿también se usa XML para eso?), O quizás una lista separada por comas.

+0

es para la importación de datos en realidad. Por eso estoy usando Regex. Para asegurarse de que los datos importados se ajusten al formato dado especificado por el usuario. –

+0

Cuando dices que es lento, ¿qué tan lento te refieres? ¿Qué tan grandes son estos archivos? ¿Con cuántos patrones estás comparando? Es de suponer que hay algunos patrones bastante comunes que podrían hacerse utilizando ifs y substrings. Probablemente sea más eficiente (aunque menos claro) si en lugar de usar RegEx para patrones comunes, usted mismo los codifica en la aplicación. Por cierto, ¿esta funcionalidad se usa regularmente? De ser así, debe haber una manera más eficiente de almacenar esta información donde quiera que vaya directamente (¿base de datos?), Realizando la validación entonces, en la entrada. –

4

La expresión regular es costosa de crear y es incluso más costosa si la compila. Entonces, el problema es que estás creando muchas expresiones regulares, pero úsalas solo una vez.

Debe almacenarlos en caché para su reutilización y, de hecho, no compilarlos si no desea utilizarlos muy a menudo. Nunca lo he medido, pero podría imaginar que tendrá que usar una expresión regular simple más de 100 veces para sobrepasar el costo de la compilación.

Prueba de rendimiento

  • Regex: "^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+(?:[a-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)$"

  • de entrada: "www.stackoverflow.com"

  • Resultados en milisegundos por iteración

    • una expresión regular , compilados, 10.000 iteraciones: 0,0018 ms
    • uno de expresiones regulares, no compilado, 10.000 iteraciones: 0,0021 ms
    • una expresión regular por iteración, no compilado, 10.000 iteraciones: 0,0287 ms
    • una expresión regular por iteración , compilados, 10.000 iteraciones: 4,8144 ms

Tenga en cuenta que incluso después de 10, 000 iteraciones, las expresiones regulares compiladas y no compiladas aún están muy juntas comparando su rendimiento. Con un número creciente de iteraciones, la expresión regular compilada tiene un mejor rendimiento.

  • uno de expresiones regulares, compilados, 1.000.000 iteraciones: 0,00137 ms
  • uno de expresiones regulares, no compilado, 1.000.000 iteraciones: 0,00225 ms
+0

Quizás necesitaba explicar un poco mejor. No los estoy usando solo una vez. Cada vez que algo durante el archivo analizado apunta a un diseño específico. Compruebo que la línea coincida con un patrón para ese diseño. –

+0

La precompilación, incluso para un solo uso, en mi prueba arroja un rendimiento de expresiones regulares consistentemente mejor. No es mucho para un solo uso, pero no hay un golpe de rendimiento. – patjbs

+0

Entonces usted crea una expresión regular por diseño y la utiliza cuando encuentra una línea correspondiente, ¿verdad? –

5

algunos pensamientos de rendimiento:

  • use [01] en lugar de (0|1)
  • uso de grupos sin fines de captura de (?:expr) en lugar de grupos de captura (si realmente necesita agrupación)

Editar Como parece que sus valores están separados por espacios en blanco, por qué no hacer ¿Lo dividiste allí?

+0

Ya, en realidad puede ser más beneficioso mantener una lista de expresiones regulares más pequeñas por diseño, y dividir la cadena en función de '\ t' y luego hacer coincidir cada una. –

1

Solo construiría un lexer a mano.

En este caso, parece que tiene un grupo de campos separados por pestañas, con un registro terminado por una nueva línea. El archivo XML parece describir la secuencia de columnas y sus tipos.

Escribir código para reconocer cada caso a mano es probablemente 5-10 líneas de código en el peor de los casos.

En este caso, querrá simplemente generar un arraay de valores de PrimitiveType [] del archivo xml, y luego llamar a la función "GetValues" a continuación.

Esto debería permitirle hacer una sola pasada a través del flujo de entrada, lo que debería dar un gran impulso al uso de expresiones regulares.

Deberá proporcionar los métodos "ScanXYZ" usted mismo. Deberían ser fáciles de escribir. Lo mejor es implementarlos sin utilizar expresiones regulares.

public IEnumerable<object[]> GetValues(TextReader reader, PrimitiveType[] schema) 
{ 
    while (reader.Peek() > 0) 
    { 
     var values = new object[schema.Length]; 
     for (int i = 0; i < schema.Length; ++i) 
     { 
      switch (schema[i]) 
      { 
       case PrimitiveType.BIT: 
        values[i] = ScanBit(reader); 
        break; 
       case PrimitiveType.DATE: 
        values[i] = ScanDate(reader); 
        break; 
       case PrimitiveType.FLOAT: 
        values[i] = ScanFloat(reader); 
        break; 
       case PrimitiveType.INTEGER: 
        values[i] = ScanInt(reader); 
        break; 
       case PrimitiveType.STRING: 
        values[i] = ScanString(reader); 
        break; 
      } 
     } 

     EatTabs(reader); 

     if (reader.Peek() == '\n') 
     { 
      break; 
     } 

    if (reader.Peek() == '\n') 
    { 
     reader.Read(); 
    } 
    else if (reader.Peek() >= 0) 
    { 
     throw new Exception("Extra junk detected!"); 
    } 

    yield return values; 

    } 

    reader.Read(); 
} 
Cuestiones relacionadas