Por qué \\d en las expresiones regulares es más lento que [0-9]? Análisis en C#

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: El mito de la velocidad de las expresiones regulares

\n\n

Si alguna vez has programado en C# y has usado expresiones regulares, seguramente te has enfrentado al dilema: usar \\d o [0-9] para buscar dígitos? A primera vista no hay diferencia — ambos encuentran dígitos. Pero los desarrolladores experimentados, que pulen el rendimiento hasta el brillo, saben: \\d puede ser significativamente más lento que [0-9].

\n\n

En Stack Overflow, esta pregunta ha acumulado miles de visitas y decenas de comentarios. Hoy no solo vamos a repetir la respuesta, sino que miraremos bajo el capó del motor .NET Regex y entenderemos por qué sucede esto y cómo afecta a tu código. Escribiremos benchmarks reales en C#, veremos el código IL y sabremos cuándo preocuparnos y cuándo podemos relajarnos.

\n\n

¿Listos? ¡Allá vamos!

\n\n

¿Qué son \\d y [0-9] en C#?

\n\n

Para empezar, lo básico. En la mayoría de las implementaciones de expresiones regulares (incluyendo C#), \\d es un atajo para "cualquier dígito". Y [0-9] es una clase de caracteres que enumera explícitamente el rango del '0' al '9'.

\n\n

Parece que es lo mismo. Pero el diablo, como siempre, está en los detalles.

\n\n

Unicode vs ASCII

\n\n

En .NET (desde .NET Framework 4.5 en adelante), \\d por defecto coincide con cualquier carácter de la categoría Unicode Nd (Number, Decimal Digit). Esto no solo incluye los dígitos arábigos 0-9, sino también:

\n
    \n
  • Dígitos devanagari (०१२३...)
  • \n
  • Dígitos de escritura tamil
  • \n
  • Dígitos arábigos orientales
  • \n
  • Y docenas de otros sistemas numéricos
  • \n
\n\n

Mientras que [0-9] es un rango fijo de códigos ASCII (48-57). Solo 0,1,2,3,4,5,6,7,8,9. Sin Unicode.

\n\n

¿Por qué la velocidad es diferente? Análisis bajo el capó

\n\n

Ahora lo principal: ¿por qué \\d es más lento? Analicémoslo a nivel del motor.

\n\n

1. Cantidad de comprobaciones

\n\n

Cuando el motor encuentra [0-9], hace una cosa: verifica si el código del carácter está en el rango 48-57. Esto es una comparación (o dos, si se consideran los límites).

\n\n

Cuando el motor encuentra \\d, debe:

\n
    \n
  1. Obtener la categoría Unicode del carácter (esto ya es más costoso que un simple int).
  2. \n
  3. Compararla con la categoría Nd.
  4. \n
\n\n

La categoría Unicode no es solo un número, es una bandera completa que se calcula a través de tablas internas. Para los dígitos latinos, el resultado será el mismo, pero el camino hacia él es más largo.

\n\n

2. Caché y JIT

\n\n

En .NET, Regex se compila en código intermedio (IL) o incluso en nativo (si se usa RegexOptions.Compiled). Para [0-9], el JIT puede generar una secuencia de instrucciones muy simple: un par de comparaciones y un salto condicional. Para \\d — una llamada al método CharUnicodeInfo.GetUnicodeCategory y una comparación posterior.

\n\n

Llamar a un método tiene sobrecarga: push de argumentos, call, verificación de pila. Incluso si el método es pequeño, sigue siendo más lento que una comparación integrada.

\n\n

3. Ejemplo de un benchmark real

\n\n

Escribamos una prueba simple en C# usando BenchmarkDotNet (asegúrate de instalarlo a través de NuGet si quieres repetirlo):

\n\n
using BenchmarkDotNet.Attributes;\nusing BenchmarkDotNet.Running;\nusing System.Text.RegularExpressions;\n\npublic class RegexBenchmark\n{\n    private static readonly Regex _digitPattern = new Regex(@"\\d", RegexOptions.Compiled);\n    private static readonly Regex _rangePattern = new Regex(@"[0-9]", RegexOptions.Compiled);\n    \n    private const string TestString = "Hello123World456!";\n\n    [Benchmark]\n    public bool DigitPattern() => _digitPattern.IsMatch(TestString);\n\n    [Benchmark]\n    public bool RangePattern() => _rangePattern.IsMatch(TestString);\n}\n\nclass Program\n{\n    static void Main() => BenchmarkRunner.Run<RegexBenchmark>();\n}
\n\n

Resultados (en .NET

Blogs

Book Recommendations