RegexOptions.Compiled
instrui o mecanismo de expressão regular a compilar a expressão regular em IL usando a geração de código leve ( LCG ). Essa compilação acontece durante a construção do objeto e o torna muito lento. Por sua vez, as correspondências usando a expressão regular são mais rápidas.
Se você não especificar esse sinalizador, sua expressão regular será considerada "interpretada".
Veja este exemplo:
public static void TimeAction(string description, int times, Action func)
{
// warmup
func();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < times; i++)
{
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
static void Main(string[] args)
{
var simple = "^\\d+$";
var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
var complex = @"^(([^<>()[\]\\.,;:\s@""]+"
+ @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
+ @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
+ @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
+ @"[a-zA-Z]{2,}))$";
string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
string[] emails = new string[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };
foreach (var item in new[] {
new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
new {Pattern = medium, Matches = emails, Name = "Simple email match"},
new {Pattern = complex, Matches = emails, Name = "Complex email match"}
})
{
int i = 0;
Regex regex;
TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
i = 0;
TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern, RegexOptions.Compiled);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern);
i = 0;
TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern, RegexOptions.Compiled);
i = 0;
TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
}
}
Ele realiza 4 testes em 3 expressões regulares diferentes. Primeiro, ele testa uma partida única (compilada vs não compilada). Segundo, ele testa correspondências repetidas que reutilizam a mesma expressão regular.
Os resultados na minha máquina (compilado na versão, sem depurador conectado)
1000 correspondências simples (construa Regex, corresponda e descarte)
Tipo | Plataforma | Número trivial | Simples verificação de e-mail | Verificação de email ext
-------------------------------------------------- ----------------------------
Interpretado | x86 4 ms | 26 ms | 31 ms
Interpretado | x64 5 ms | 29 ms | 35 ms
Compilado | x86 913 ms | 3775 ms | 4487 ms
Compilado | x64 3300 ms | 21985 ms | 22793 ms
1.000.000 correspondências - reutilizando o objeto Regex
Tipo | Plataforma | Número trivial | Simples verificação de e-mail | Verificação de email ext
-------------------------------------------------- ----------------------------
Interpretado | x86 422 ms | 461 ms | 2122 ms
Interpretado | x64 436 ms | 463 ms | 2167 ms
Compilado | x86 279 ms | 166 ms | 1268 ms
Compilado | x64 281 ms | 176 ms | 1180 ms
Esses resultados mostram que expressões regulares compiladas podem ser até 60% mais rápidas nos casos em que você reutiliza o Regex
objeto. No entanto, em alguns casos, pode ser mais de três ordens de magnitude mais lenta a ser construída.
Também mostra que a versão x64 do .NET pode ser 5 a 6 vezes mais lenta quando se trata de compilação de expressões regulares.
A recomendação seria usar a versão compilada nos casos em que
- Você não se importa com o custo de inicialização do objeto e precisa do aumento extra de desempenho. (observe que estamos falando de frações de milissegundos aqui)
- Você se preocupa um pouco com o custo de inicialização, mas está reutilizando o objeto Regex tantas vezes que ele será compensado durante o ciclo de vida do aplicativo.
Chave inglesa em obras, o cache Regex
O mecanismo de expressão regular contém um cache LRU que contém as últimas 15 expressões regulares testadas usando os métodos estáticos da Regex
classe.
Por exemplo: Regex.Replace
, Regex.Match
etc .. todos usam o cache de Regex.
O tamanho do cache pode ser aumentado pela configuração Regex.CacheSize
. Ele aceita alterações de tamanho a qualquer momento durante o ciclo de vida do seu aplicativo.
Novas expressões regulares são armazenadas em cache apenas pelos auxiliares estáticos na classe Regex. Se você construir seus objetos, o cache será verificado (para reutilização e colidido), no entanto, a expressão regular que você construir não será anexada ao cache .
Esse cache é um cache LRU trivial , é implementado usando uma lista simples de dupla ligação. Se você aumentar para 5000 e usar 5000 chamadas diferentes nos auxiliares estáticos, toda construção de expressão regular rastreará as 5000 entradas para ver se ela foi armazenada em cache anteriormente. Há uma trava ao redor da verificação, portanto, a verificação pode diminuir o paralelismo e introduzir o bloqueio de threads.
O número é muito baixo para se proteger de casos como esse, embora em alguns casos você possa não ter escolha a não ser aumentá-lo.
Minha forte recomendação seria nunca passar a RegexOptions.Compiled
opção a um ajudante estático.
Por exemplo:
\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)
O motivo é que você está arriscando muito a perda do cache LRU, o que desencadeará uma compilação muito cara . Além disso, você não tem idéia do que as bibliotecas das quais você depende estão fazendo, portanto, tem pouca capacidade de controlar ou prever o melhor tamanho possível do cache.
Veja também: blog da equipe BCL
Nota : isso é relevante para o .NET 2.0 e o .NET 4.0. Há algumas mudanças esperadas no 4.5 que podem fazer com que isso seja revisado.