Pergunta

Esta é apenas uma questão de satisfazer a minha curiosidade. Mas para mim é interessante.

Eu escrevi este pequeno e simples benchmark. Ele chama 3 variantes de execução Regexp em uma ordem aleatória alguns milhares de vezes:

Basicamente, eu uso o mesmo padrão, mas de maneiras diferentes.

  1. O seu modo ordinário sem qualquer RegexOptions. Começando com o .NET 2.0 estes não ficam em cache. Mas deve ser "em cache" porque é realizada em um escopo muito global e não redefinir.

  2. Com RegexOptions.Compiled

  3. Com uma chamada para o Regex.Match(pattern, input) estática que não se armazenado em cache no .NET 2.0

Aqui está o código:

static List<string> Strings = new List<string>();        
static string pattern = ".*_([0-9]+)\\.([^\\.])$";

static Regex Rex = new Regex(pattern);
static Regex RexCompiled = new Regex(pattern, RegexOptions.Compiled);

static Random Rand = new Random(123);

static Stopwatch S1 = new Stopwatch();
static Stopwatch S2 = new Stopwatch();
static Stopwatch S3 = new Stopwatch();

static void Main()
{
  int k = 0;
  int c = 0;
  int c1 = 0;
  int c2 = 0;
  int c3 = 0;

  for (int i = 0; i < 50; i++)
  {
    Strings.Add("file_"  + Rand.Next().ToString() + ".ext");
  }
  int m = 10000;
  for (int j = 0; j < m; j++)
  {
    c = Rand.Next(1, 4);

    if (c == 1)
    {
      c1++;
      k = 0;
      S1.Start();
      foreach (var item in Strings)
      {
        var m1 = Rex.Match(item);
        if (m1.Success) { k++; };
      }
      S1.Stop();
    }
    else if (c == 2)
    {
      c2++;
      k = 0;
      S2.Start();
      foreach (var item in Strings)
      {
        var m2 = RexCompiled.Match(item);
        if (m2.Success) { k++; };
      }
      S2.Stop();
    }
    else if (c == 3)
    {
      c3++;
      k = 0;
      S3.Start();
      foreach (var item in Strings)
      {
        var m3 = Regex.Match(item, pattern);
        if (m3.Success) { k++; };
      }
      S3.Stop();
    }
  }

  Console.WriteLine("c: {0}", c1);
  Console.WriteLine("Total milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString());
  Console.WriteLine("Adjusted milliseconds: " + (S1.Elapsed.TotalMilliseconds).ToString());

  Console.WriteLine("c: {0}", c2);
  Console.WriteLine("Total milliseconds: " + (S2.Elapsed.TotalMilliseconds).ToString());
  Console.WriteLine("Adjusted milliseconds: " + (S2.Elapsed.TotalMilliseconds*((float)c2/(float)c1)).ToString());

  Console.WriteLine("c: {0}", c3);
  Console.WriteLine("Total milliseconds: " + (S3.Elapsed.TotalMilliseconds).ToString());
  Console.WriteLine("Adjusted milliseconds: " + (S3.Elapsed.TotalMilliseconds*((float)c3/(float)c1)).ToString());
}

Toda vez que eu chamo-lhe o resultado é ao longo das linhas de:

    Not compiled and not automatically cached:
    Total milliseconds: 6185,2704
    Adjusted milliseconds: 6185,2704

    Compiled and not automatically cached:
    Total milliseconds: 2562,2519
    Adjusted milliseconds: 2551,56949184038

    Not compiled and automatically cached:
    Total milliseconds: 2378,823
    Adjusted milliseconds: 2336,3187176891

Então, você tem isso. Não muito, mas cerca de 7-8% de diferença.

Não é o único mistério. Eu não posso explicar por que a primeira maneira seria muito mais lento porque ele nunca é reavaliado, mas realizada em uma variável estática global.

A propósito, este é em .Net 3.5 e Mono 2.2 que se comportam exatamente o mesmo. No Windows.

Assim, todas as idéias, porque a variante compilado sequer caem para trás?

EDIT1:

Depois de corrigir o código os resultados agora parecido com este:

    Not compiled and not automatically cached:
    Total milliseconds: 6456,5711
    Adjusted milliseconds: 6456,5711

    Compiled and not automatically cached:
    Total milliseconds: 2668,9028
    Adjusted milliseconds: 2657,77574842168

    Not compiled and automatically cached:
    Total milliseconds: 6637,5472
    Adjusted milliseconds: 6518,94897724836

que praticamente torna obsoletas todas as outras questões também.

Obrigado pelas respostas.

Foi útil?

Solução

Na versão Regex.Match você está olhando para a entrada no padrão. Tente trocar os parâmetros redor.

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct

Outras dicas

notei semelhante comportamento. Eu também queria saber por que a versão compilada seria mais lento, mas notou que acima de um determinado número de chamadas, a versão compilada é mais rápido. Então, eu me aprofundei refletor um pouco, e eu notei que um Regex compilado, ainda há um pouco de configuração que é realizada em primeira convocação (especificamente, a criação de uma instância do apropriado RegexRunner objeto).

No meu teste, eu descobri que se eu me mudei tanto o construtor e uma chamada de usar e deitar fora inicial para o regex fora do início do temporizador, a regex compilado ganhou, não importa quantas iterações eu corri.


A propósito, o caching que o quadro está fazendo quando se utiliza métodos Regex estáticos é uma otimização que é apenas necessária quando se utiliza métodos Regex estáticos. Isso ocorre porque cada chamada para um método Regex estática cria um novo objeto Regex. No construtor da classe Regex deve analisar o padrão. O cache permite que as chamadas subseqüentes de métodos Regex estáticos para reutilizar o RegexTree analisado a partir da primeira chamada, evitando assim a etapa de análise.

Quando você usa métodos de instância em um único objeto Regex, então este não é um problema. A análise ainda é realizada apenas uma vez (quando você cria o objeto). Além disso, você começa a evitar a execução de todo o outro código no construtor, bem como a alocação de pilha (e posterior coleta de lixo).

Martin Brown notado que você reverteu os argumentos para a sua chamada Regex estático (boa captura, Martin). Acho que você vai achar que se você corrigir isso, a instância (não-compilado) regex vai bater a estática chama cada vez. Você também deve saber que, dadas as minhas conclusões acima, o exemplo compilado vai bater o não-compilados, também.

MAS : Você realmente deve ler post de Jeff Atwood sobre expressões regulares compilados antes de ir cegamente aplicar essa opção a cada regex que você criar.

Se você combinar constantemente o mesmo string usando o mesmo padrão, que pode explicar por que uma versão em cache é ligeiramente mais rápido do que uma versão compilada.

Esta é a partir de documentação;

https://msdn.microsoft.com /en-us/library/gg578045(v=vs.110).aspx

quando uma expressão regular estática método é chamado e regular expressão não pode ser encontrada no cache, o mecanismo de expressão regular converte a expressão regular a um conjunto de códigos de operação e lojas -los no cache . Em seguida, ele converte esses códigos de operação para MSIL assim que o compilador JIT pode executá-los. regulares Interpretada expressões reduzir o tempo de inicialização ao custo de tempo de execução mais lenta . Devido a isso, eles são melhor utilizada quando a expressão regular é usado em um pequeno número de chamadas de método , ou se o número exato de chamadas para métodos de expressão regular é desconhecida, mas é esperado para ser pequeno. Como o número de chamadas de método aumenta, o ganho de desempenho do tempo de inicialização reduzido é superado pela execução mais lenta velocidade.

Em contraste com expressões regulares interpretados, compilado regulares expressões aumentar o tempo de inicialização, mas executar indivíduo métodos de correspondência de padrão mais rápido . Como resultado, o benefício de desempenho que resulta da compilação dos aumentos de expressão regular na proporcionalmente ao número de métodos de expressão regular chamados.


Resumindo, nós recomendamos que você use interpretados expressões regulares quando você chamar métodos de expressão regular com uma específica expressão regular com pouca frequência.

Você deve usar expressões regulares compilados quando você chamar regulares métodos de expressão com uma expressão regular específica relativamente freqüentemente.


Como detectar?

O limite exato em que a execução mais lenta velocidades de expressões regulares interpretados superam os ganhos de sua reduzida tempo de inicialização, ou o limite no qual os tempos de inicialização mais lento de expressões regulares compilados superam os ganhos de sua mais rápido velocidades de execução, é difícil de determinar. Ele depende de uma variedade de fatores, incluindo a complexidade da expressão regular ea dados específicos que processa. Para determinar se interpretado ou expressões regulares compilados oferecer o melhor desempenho para o seu determinado cenário de aplicação, você pode usar a classe Cronômetro para comparar seus tempos de execução .


Compilado expressões regulares:

Recomendamos que você compilar expressões regulares para uma montagem no nas seguintes situações:

  1. Se você é um desenvolvedor de componentes que quer para criar uma biblioteca de expressões regulares reutilizáveis.
  2. Se você espera métodos de correspondência de padrão da sua expressão regular para ser chamado número indeterminado de vezes - em qualquer lugar de uma vez ou duas vezes para milhares ou dezenas de milhares de vezes. Ao contrário compilado ou expressões regulares interpretados, expressões regulares que são compilados para conjuntos separados oferecer desempenho que é consistente, independentemente do número de chamadas de método.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top