Почему кэшированный Regexp превосходит скомпилированный? - PullRequest
9 голосов
/ 09 января 2009

Это просто вопрос для удовлетворения моего любопытства. Но мне это интересно.

Я написал этот маленький простой тест. Он вызывает 3 варианта выполнения Regexp в случайном порядке несколько тысяч раз:

В основном я использую один и тот же шаблон, но по-разному.

  1. Ваш обычный путь без RegexOptions. Начиная с .NET 2.0 они не кэшируются. Но его следует «кэшировать», поскольку он хранится в довольно глобальной области видимости и не сбрасывается.

  2. С RegexOptions.Compiled

  3. При вызове статического Regex.Match(pattern, input), который кэшируется в .NET 2.0

Вот код:

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());
}

Каждый раз, когда я называю это, результат примерно такой:

    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

Так что у вас это есть. Не сильно, но разница около 7-8%.

Это не единственная загадка. Я не могу объяснить, почему первый способ был бы намного медленнее, потому что он никогда не переоценивается, а хранится в глобальной статической переменной.

Кстати, это на .Net 3.5 и Mono 2.2, которые ведут себя точно так же. В Windows.

Итак, есть идеи, почему скомпилированный вариант будет отставать?

EDIT1:

После исправления кода результаты теперь выглядят так:

    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

Что в значительной степени устарело и для всех остальных вопросов.

Спасибо за ответы.

Ответы [ 4 ]

4 голосов
/ 09 января 2009

В версии Regex.Match вы ищете вход в шаблон. Попробуйте поменять параметры вокруг.

var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct
3 голосов
/ 09 января 2009

Я заметил подобное поведение. Я также задавался вопросом, почему скомпилированная версия будет медленнее, но заметил, что при превышении определенного количества вызовов скомпилированная версия работает быстрее. Поэтому я немного покопался в Reflector и заметил, что для скомпилированного Regex еще есть небольшая настройка, которая выполняется при первом вызове (в частности, создание экземпляра соответствующего RegexRunner объект).

В моем тесте я обнаружил, что если я переместил и конструктор, и начальный одноразовый вызов в регулярное выражение вне запуска таймера, скомпилированное регулярное выражение выиграло независимо от того, сколько итераций я выполнил.


Кстати, кеширование, которое фреймворк делает при использовании статических методов Regex, является оптимизацией, которая необходима только при использовании статических методов Regex. Это связано с тем, что каждый вызов статического метода Regex создает новый объект Regex. В конструкторе класса Regex он должен анализировать шаблон. Кэширование позволяет последующим вызовам статических методов Regex повторно использовать RegexTree, проанализированный с первого вызова, тем самым избегая шага синтаксического анализа.

Когда вы используете методы экземпляра для одного Regex объекта, это не проблема. Разбор все еще выполняется только один раз (при создании объекта). Кроме того, вы можете избежать запуска всего остального кода в конструкторе, а также выделения кучи (и последующей сборки мусора).

Мартин Браун заметил , что вы перевернули аргументы для своего статического Regex вызова (хороший улов, Мартин). Я думаю, вы обнаружите, что если вы это исправите, экземпляр (не скомпилированный) регулярное выражение будет каждый раз побеждать статические вызовы. Вы также должны заметить, что, учитывая мои выводы выше, скомпилированный экземпляр также превзойдет не скомпилированный.

НО : Вы действительно должны прочитать пост Джеффа Этвуда о скомпилированных регулярных выражениях, прежде чем слепо применять эту опцию к каждому создаваемому регулярному выражению.

0 голосов
/ 09 марта 2016

Это из документации;

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

когда вызывается статическое регулярное выражение метод и регулярное выражение не может быть найдено в кеше, механизм регулярных выражений преобразует регулярное выражение в набор кодов операций и сохраняет их в кеше . Затем он преобразует эти коды операций в MSIL так, что JIT-компилятор может их выполнить. Устный перевод выражения сокращают время запуска за счет более медленного времени выполнения . Из-за этого они лучше всего используются, когда регулярное выражение используется в небольшом количестве вызовов методов , или если точное число вызовы методов регулярных выражений неизвестны, но ожидается, что маленький. По мере увеличения числа вызовов методов прирост производительности из-за уменьшенного времени запуска опережает более медленное выполнение скорость.

В отличие от интерпретируемых регулярных выражений, скомпилированный регулярный выражения увеличивают время запуска, но выполняют отдельные методы сопоставления с образцом быстрее . В результате выигрыш в производительности что в результате компиляции регулярного выражения увеличивается в пропорционально количеству вызываемых методов регулярного выражения.


Подводя итог, мы рекомендуем использовать интерпретируемые регулярные выражения при вызове методов регулярных выражений с определенным регулярное выражение относительно нечасто.

Вы должны использовать скомпилированные регулярные выражения при вызове регулярных методы выражения с определенным регулярным выражением относительно часто.


Как обнаружить?

Точный порог, при котором медленные скорости выполнения интерпретированные регулярные выражения перевешивают выгоды от их уменьшенного время запуска или порог, при котором более медленное время запуска скомпилированные регулярные выражения перевешивают выгоды от их быстрее Скорости исполнения, определить сложно. Это зависит от разнообразия факторов, включая сложность регулярного выражения и конкретные данные, которые он обрабатывает. Чтобы определить, интерпретируется ли или скомпилированные регулярные выражения предлагают лучшую производительность для вашего конкретный сценарий приложения, вы можете использовать класс секундомера для сравнить время их исполнения .


Скомпилированные регулярные выражения:

Рекомендуется компилировать регулярные выражения для сборки в следующие ситуации:

  1. Если вы разработчик компонента, который хочет создать библиотеку многоразовых регулярных выражений.
  2. Если вы ожидаете методы сопоставления с образцом вашего регулярного выражения, которые будут называться неопределенное количество раз - от одного до двух тысячи или десятки тысяч раз. В отличие от скомпилированного или интерпретируемые регулярные выражения, регулярные выражения, которые компилируются Отдельные сборки предлагают производительность, которая является последовательной независимо от количества вызовов методов.
0 голосов
/ 09 января 2009

Если вы постоянно сопоставляете одну и ту же строку, используя один и тот же шаблон, это может объяснить, почему кэшированная версия немного быстрее, чем скомпилированная версия.

...