2010-12-27 11 views
18

Tengo una regex de C# grande y compleja que funciona bien cuando se interpreta, pero es un poco lenta. Estoy tratando de acelerar esto configurando RegexOptions.Compiled, y esto parece tomar unos 30 segundos por primera vez e inmediatamente después de eso. Intento negar esto compilando la expresión regular a un ensamblado primero, por lo que mi aplicación puede ser lo más rápida posible.¿Por qué mi expresión regular es mucho más lenta que la compilada?

Mi problema es cuando el retraso compilación se lleva a cabo, ya sea compilada en la aplicación:

Regex myComplexRegex = new Regex(regexText, RegexOptions.Compiled); 
MatchCollection matches = myComplexRegex.Matches(searchText); 
foreach (Match match in matches) // <--- when the one-time long delay kicks in 
{ 

} 

o el uso de Regex.CompileToAssembly con antelación:

MatchCollection matches = new CompiledAssembly.ComplexRegex().Matches(searchText); 
foreach (Match match in matches) // <--- when the one-time long delay kicks in 
{ 

} 

Esto está haciendo compilando a un ensamblaje básicamente inútil, ya que todavía tengo el retraso en la primera llamada foreach. Lo que quiero es que todo el retraso de compilación se realice en tiempo de compilación (en la llamada Regex.CompileToAssembly) y no en tiempo de ejecución. ¿Dónde estoy equivocado?

(El código que estoy utilizando para compilar un ensamblado es similar al http://www.dijksterhuis.org/regular-expressions-advanced/, si es relevante).

Editar:

debo utilizar new al llamar al ensamblado compilado en new CompiledAssembly.ComplexRegex().Matches(searchText);? Sin embargo, da un error de "referencia de objeto requerido" sin él.

Actualización 2

Gracias por las respuestas/comentarios. La expresión regular que estoy usando es bastante larga, pero básicamente sencilla, una lista de miles de palabras, cada una separada por |. No puedo ver que sería un problema de retroceso realmente. La cadena de asunto puede tener una sola letra de longitud, y aún puede causar el retraso de compilación. Para una expresión regular RegexOptions.Compiled, tardará más de 10 segundos en ejecutarse cuando la expresión regular contenga 5000 palabras. A modo de comparación, la versión no compilada de la expresión regular puede tomar más de 30,000 palabras y aun así ejecutarse casi al instante.

Después de hacer muchas pruebas sobre este tema, lo que creo que he descubierto es:

  • no utilice RegexOptions.Compiled cuando su expresión regular tiene muchas alternativas - que puede ser muy lento para que compilar.
  • .Net usará lazy evaluation for regex cuando sea posible, y AFAI puede ver que esto se extiende (al menos hasta cierto punto) también a la compilación de expresiones regulares. Una expresión regular se compilará por completo solo cuando deba serlo, y parece que no hay forma de forzar la compilación antes de tiempo.
  • Regex.CompileToAssembly sería mucho más útil si se pudiera obligar a las expresiones regulares a compilarse por completo, parece que ya casi no tiene sentido.

¡Corríjame si me equivoco o extraño algo!

+0

Quizás deba intentar compartir la expresión real involucrada y una entrada de muestra, que le da este comportamiento. – driis

+1

Gracias por esta publicación. Tuve el mismo problema con algunos Regex de Twitter portado desde Java a .NET. Tanto RegexOptions.Compiled como .CompileToAssembly causaron que la aplicación se cuelgue durante ~ 10 segundos la primera vez que intenta coincidir. Se ha eliminado Regex.Compiled y todo es instantáneo. – LongZheng

+2

MSDN agregó un artículo de mejores prácticas para .NET 4 que se refería a [compilaciones de regex compiladas] (http://msdn.microsoft.com/en-us/library/gg578045.aspx#sectionToggle4). –

Respuesta

2

Pruebe usar Regex.CompileToAssembly, luego vincule al ensamblaje para que pueda construir los objetos Regex. RegexOptions.Compiled es una opción de tiempo de ejecución, la expresión regular aún se volverá a compilar cada vez que ejecute la aplicación.

+0

Esto es lo que estoy haciendo ya, lo siento si eso no está claro. El comando CompileToAssembly funciona casi instantáneamente (pensé que debería tomar un poco de tiempo), y recibí el retraso en el foreach en el momento en que ejecutaba la expresión regular. – mikel

+3

¿Podría actualizar su pregunta para mostrar eso? Si está haciendo 'new Regex', entonces está construyendo nuevas instancias Regex sin compilar. Tendrá que usar las clases en su conjunto de expresiones regulares. – Douglas

+0

Gracias Douglas, lo he actualizado, así que espero que esté un poco más claro ahora. – mikel

7

Al usar RegexOptions.Compiled, debe asegurarse de volver a utilizar el objeto Regex. No parece que estés haciendo esto.

RegexOptions.Compiled es una compensación. La construcción inicial de Regex será más lenta, porque el código se compila sobre la marcha, pero cada coincidencia debería ser más rápida. Si su expresión regular cambia en tiempo de ejecución, probablemente no se beneficie al usar RegexOptions.Compiled, aunque podría depender de la expresión real involucrada.

actualización, por los comentarios

Si su código real se parece a la que usted ha publicado, usted no está tomando ventaja de CompileToAssembly, como se va a crear nuevos, sobre la marcha de los casos compilados de expresiones regulares cada tiempo que se ejecuta esa pieza de código. Para poder aprovechar CompileToAssembly, primero deberá compilar Regex; luego tome el conjunto generado y haga referencia a él en su proyecto. A continuación, debe crear una instancia de los tipos Regex generados y fuertemente tipados generados.

En el ejemplo al que se vincula, tiene una expresión regular llamada FindTCPIP, que se compila en un tipo denominado FindCTPIP. Cuando esto tiene que ser utilizado, se debe crear una nueva instancia de este tipo específico, tales como:

TheRegularExpressions.FindTCPIP MatchTCP = new TheRegularExpressions.FindTCPIP(); 
+0

¿Es posible tener la demora involucrada en la construcción inicial transferida al tiempo Regex.CompileToAssembly? Pensé que la idea general de CompileToAssembly era eliminar la lentitud de la construcción del tiempo de ejecución al tiempo de compilación, por lo que no habría ninguna compensación. – mikel

+0

¿El código publicado en su pregunta es el código real que está utilizando? En ese caso, CompileToAssembly no tendrá ningún efecto, porque está creando nuevas instancias Regex cada vez. – driis

+0

Disculpa por no ser clara, desde entonces he editado la pregunta. Intenté crear nuevos objetos Regex() o crear instancias como en el ejemplo y ninguno funciona para mí. Todavía hay una gran demora la primera vez que se ejecuta la expresión regular de cualquier forma, y ​​eso es lo que trato de transferir del tiempo de ejecución al tiempo de compilación. – mikel

2

Una causa muy probable cuando se investiga una expresión regular lenta es que se da marcha atrás demasiado. Esto se resuelve reescribiendo la expresión regular para que el número de retroceso sea inexistente o mínimo.

Puede publicar la expresión regular y una entrada de muestra donde sea lenta.

Personalmente, no tuve la necesidad de compilar una expresión regular aunque es interesante ver algunos números reales sobre el rendimiento si ha seguido este camino.

+0

Gracias, he actualizado la publicación con más detalles. No creo que sea un problema de retroceso, ya que es muy rápido sin el indicador RegexOptions.Compiled allí. – mikel

+0

Tenga en cuenta que es muy fácil escribir expresiones regulares ineficaces (casi demasiado fácil). Si la expresión regular es básicamente un conjunto de alternancias, hay poco que optimizar (pero hay espacio). Tenga cuidado porque puede tomar solo 1 carácter para causar "Retroceso Catastrófico" http://www.regular-expressions.info/catastrophic.html Todavía estoy interesado en su expresión regular y en los casos lentos para echar un vistazo . – buckley

+0

También es posible deshabilitar el rastreo retroactivo utilizando el elemento de lenguaje '(?> Subexpression)'. Se puede encontrar más información sobre esto en [Mejores prácticas para expresiones regulares en .NET Framework] (http://msdn.microsoft.com/en-us/library/gg578045%28v=vs.110%29.aspx# Retroceso) en MSDN. – Ronald

1

Para forzar la inicialización puede llamar a Match contra una cadena vacía. Además de eso, puedes usar ngen para crear una imagen nativa de la expresión para acelerar aún más el proceso. Pero probablemente lo más importante es esencialmente igual de rápido para lanzar 30,000 cadenas.IndexOf o string.Contains o Regex.Match declaraciones contra un texto dado, que compilar una gran expresión descomunal para Match contra un solo texto. Dado que eso requiere mucha menos compilación, jits, etc., ya que la máquina de estado es mucho más simple.

Otra cosa que podría considerar es tokenizar el texto y cortarlo con la lista de palabras que busca.

Cuestiones relacionadas