Почему кэшированное регулярное выражение превосходит скомпилированное?
-
06-07-2019 - |
Вопрос
Это просто вопрос для удовлетворения моего любопытства.Но мне это интересно.
Я написал этот небольшой простой тест.Он вызывает 3 варианта выполнения Regexp в случайном порядке несколько тысяч раз:
По сути, я использую один и тот же шаблон, но по-разному.
Ваш обычный путь без каких-либо
RegexOptions
.Начиная с .NET 2.0, они не кэшируются.Но его следует «кэшировать», поскольку он хранится в довольно глобальном масштабе и не сбрасывается.С
RegexOptions.Compiled
С вызовом статики
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 мог выполнить их. Интерпретируемые регулярные выражения сокращают время запуска за счет более медленного времени выполнения.Из-за этого они Лучше всего использовать, когда регулярное выражение используется в небольшом количестве вызовов методов, или если точное количество вызовов к методам регулярного выражения неизвестно, но, как ожидается, будет небольшим.По мере увеличения количества вызовов методов увеличение производительности от сокращенного времени запуска превышает более медленную скорость выполнения.
В отличие от интерпретируемых регулярных выражений, Скомпилированные регулярные выражения увеличивают время запуска, но быстрее выполнять отдельные методы сопоставления схем.В результате выгода от производительности, возникающая в результате составления регулярного выражения, увеличивается пропорционально количеством называемых методов регулярного выражения.
Подводя итог, мы рекомендуем вам использовать интерпретируемые регулярные выражения Когда вы называете методы регулярного выражения с определенным регулярным выражением относительно редко.
Вы должны использовать скомпилированные регулярные выражения Когда вы называете методы регулярного выражения с определенным регулярным выражением относительно часто.
Как обнаружить?
Точный порог, при котором более медленные скорости исполнения интерпретации регулярных выражений перевешивают прирост своего сокращенного времени запуска, или пороговое значение, с которым трудно определить время более медленного запуска регулярных выражений.Это зависит от множества факторов, включая сложность регулярного выражения и конкретные данные, которые он обрабатывает. Чтобы определить, предлагают ли интерпретированные или составленные регулярные выражения наилучшую производительность для вашего конкретного сценария приложения, вы можете использовать класс секунды для сравнения их времени выполнения.
Скомпилированные регулярные выражения:
Мы рекомендуем вам компилировать регулярные выражения в сборку в следующих ситуациях:
- Если вы являетесь разработчиком компонентов, который хочет создать библиотеку многоразовых регулярных выражений.
- Если вы ожидаете, что методы соответствия моделяции вашего регулярного выражения будут называться неопределенным количеством раз-от раз или два раза до тысяч или десятков тысяч раз.В отличие от скомпилированных или интерпретированных регулярных выражений, регулярные выражения, которые собираются для отдельных сборок, обеспечивают производительность, которая является согласованной независимо от количества вызовов методов.