Recentemente eu li o fantástico Expressões Regulares – Uma Abordagem Divertida de Aurélio Marinho Jargas (@oreio e http://www.aurelio.net), buscando mais informações a respeito do uso de Regular Expressions em parsers HTML (como os que uso para criar mecanismos de busca). Não posso dar mais detalhes sobre o projeto, mas o que originou este post foi a dúvida que o livro me deixou: até que ponto vale a pena utilizar expressões regulares em um sistema para realizar parsing de HTML ao invés dos métodos existentes na classe string, como Replace, IndexOf, Split etc?
Este post relata algumas experiências que tive durante os testes, informações estas que não encontrei na net e tive de fazer por mim mesmo!
O que são Expressões Regulares?
Para quem não sabe do que se trata, segue um trecho retirado do próprio livro, que a meu ver, é uma excelente definição:
Bem resumido, uma expressão regular é um método formal de se especificar um padrão de texto.
Mais detalhadamente, é uma composição de símbolos, caracteres com funções especiais, que, agrupados entre e si e com caracteres literais, formam uma sequência, uma expressão. Essa expressão é interpretada como uma regra, que indicará sucesso se uma entrada de dados qualquer casar (match) com essa regra, ou seja, obedecer exatamente a todas suas condições.
Quer fazer uma mini-aula de Regex comigo? Dá uma olhada no vídeo abaixo!
O que é um benchmark?
Este é o primeiro de muitos benchmarks que pretendo fazer. Sempre gostei do assunto de desempenho computacional e gosto de criar códigos que realmente utilizem todo o poder de uma linguagem e de uma máquina. Benchmarks nada mais são do que testes de performance entre soluções concorrentes.
Benchmarks de peças de hardware existem aos montes pela Internet, incluindo o fantástico site Tom’s Hardware que é fonte recomendadíssima de consulta para quem quer comprar uma nova placa de vídeo ou processador. Porém benchmarks de código são mais difíceis de achar, principalmente em língua portuguesa. Espero agregar valor aos visitantes do blog com conteúdo exclusivo do que eu chamo de C# Tuning!
Let’s do it!
Todos os testes foram feitos sobre um arquivo HTML contendo 206KB em 2073 linhas. A máquina onde rodaram os testes é meu PC comum, um Athlon X2 2.2GHz com 2GB RAM. O projeto era um Console Application utilizando o .NET Framework 4.0.
Vamos ao que interessa, os testes realizados foram:
Vamos lá!
1º Cenário: Busca por Palavras
No primeiro embate da noite tivemos uma busca por uma determinada palavra dentro do HTML. Esta palavra estava na (teoricamente) pior posição do arquivo: na última linha, última coluna. Foi feito um código utilizando os métodos tradicionais de busca por string, Substring e IndexOf. O IndexOf me dava a posição daquela palavra para poder retirá-la de dentro do HTML usando o substring. Ou seja, você tem a palavra e quer encontrá-la no texto (igual aos que os editores de texto fazem). Já o Regex usa apenas seu comando Match passando por parâmetro a entrada (o HTML) e o padrão (a expressão regular). Vitória esmagadora da Regex: 24.316 Ticks no método tradicional contra 3.278 Ticks da Regex. Quase 10x mais rápido.
Segue o código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
string html = ""; //substring Stopwatch sw = Stopwatch.StartNew(); sw.Start(); string substr = html.Substring(html.IndexOf("LUIZTOOLS"), ("LUIZTOOLS").Length); sw.Stop(); long l1 = sw.ElapsedTicks; //Regex sw = Stopwatch.StartNew(); sw.Start(); string substr2 = Regex.Match(html, "(LUIZTOOLS)").Value; sw.Stop(); long l2 = sw.ElapsedTicks; //imprimindo Console.WriteLine("O comando String.Substring demorou " + l1 + " Ticks."); Console.WriteLine("O comando Regex.Match demorou " + l2 + " Ticks."); |
No 2º round temos um desafio um pouco maior: o usuário quer encontrar determinada palavra no texto e trocá-la por outra palavra. Mais uma vez, a palavra que se busca estará no final do HTML, para forçar os dois métodos a usarem o máximo de seu potencial, enfrentando o pior caso possível. No método tradicional temos o uso do método String.Replace, e no córner azul temos o Regex.Replace. Tim-tim! Empate técnico: o Replace tradicional obteve um tempo de 7753 Ticks, contra 7859 Ticks do seu rival mais elaborado.
Segue o código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
string html = ""; //replace Stopwatch sw = Stopwatch.StartNew(); sw.Start(); string substr = html.Replace("LUIZTOOLS", "123456789"); sw.Stop(); long l1 = sw.ElapsedTicks; //Regex sw = Stopwatch.StartNew(); sw.Start(); Regex r = new Regex("(LUIZTOOLS)"); string substr2 = r.Replace(html, "123456789"); sw.Stop(); long l2 = sw.ElapsedTicks; //imprimindo Console.WriteLine("O comando String.Replace demorou " + l1 + " Ticks."); Console.WriteLine("O comando Regex.Replace demorou " + l2 + " Ticks."); |
3º Cenário: Quebra de Palavras
No último embate da noite, temos o duelo de Splits. Quem sairá melhor: String.Split ou Regex.Split? Dessa vez a palavra que será usada como padrão para a “quebra” estará uma linha antes da última linha do HTML, para exigir ao máximo dos métodos. Ready…Go! Nocaute da Regex: o comando Split da Regex conseguiu a façanha de quebrar o texto em três pedaços (sim, a Regex inclui a palavra buscada dentro do array de retorno) em apenas 5.354 Ticks, contra 13.351 Ticks do Split tradicional, que traz apenas 2 posições no array.
Segue o código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
string html = ""; //replace Stopwatch sw = Stopwatch.StartNew(); sw.Start(); string[] substr = html.Split(new string[] { "LUIZTOOLS" }, StringSplitOptions.None); sw.Stop(); long l1 = sw.ElapsedTicks; //Regex sw = Stopwatch.StartNew(); sw.Start(); Regex r = new Regex("(LUIZTOOLS)"); string[] substr2 = r.Split(html); sw.Stop(); long l2 = sw.ElapsedTicks; //imprimindo Console.WriteLine("O comando String.Split demorou " + l1 + " Ticks."); Console.WriteLine("O comando Regex.Split demorou " + l2 + " Ticks."); |
Ok, você pode achar que o método Split da Regex não corresponde de forma idêntica ao Split da String, porém, note que o Split da Regex te dá outros incríveis recursos como delimitar o número máximo de posições no array, iniciar a quebra do texto a partir de uma posição específica, iniciar da direita para a esquerda, entre outras. Sem contar que a versatilidade de descrever o seu separador usando um expressão regular ao invés d euma palavra literal não tem preço.
Conclusões
Regex na veia!
Ok, se escreve mais linhas e os ganhos são imperceptíveis em aplicações tradicionais, mas não se esqueça de usar Regex caso suas aplicações processem texto pesado (como parsers), mesmo que você não esteja buscado uma palavra que atenda a uma regra elaborada, mas uma simples palavra literal como utilizada nos exemplos.
Você fez um código mais otimizado para tentar provar que os métodos tradicionais são mais rápidos que a Regex? Mande pra mim!
Você tem outra idéia de embate entre String vs Regex que gostaria de ver publicado aqui? Mande pra mim também!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.