Почему кэшированное регулярное выражение превосходит скомпилированное?

StackOverflow https://stackoverflow.com/questions/428196

Вопрос

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

Я написал этот небольшой простой тест.Он вызывает 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.

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

РЕДАКТИРОВАТЬ1:

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

    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

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

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

Это было полезно?

Решение

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

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

Другие советы

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

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


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

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

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

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

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

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

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

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

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


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

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


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

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


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

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

  1. Если вы являетесь разработчиком компонентов, который хочет создать библиотеку многоразовых регулярных выражений.
  2. Если вы ожидаете, что методы соответствия моделяции вашего регулярного выражения будут называться неопределенным количеством раз-от раз или два раза до тысяч или десятков тысяч раз.В отличие от скомпилированных или интерпретированных регулярных выражений, регулярные выражения, которые собираются для отдельных сборок, обеспечивают производительность, которая является согласованной независимо от количества вызовов методов.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top