Это просто вопрос, чтобы удовлетворить мое любопытство. Но мне это интересно.
Я написал этот небольшой простой тест. Он вызывает 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 все еще есть небольшая настройка, которая выполняется при первом вызове (в частности, создание экземпляра соответствующего объекта RegexRunner).
В своем тесте я обнаружил, что если я переместил и конструктор, и исходный вызов выброса в регулярное выражение за пределами запуска таймера, скомпилированное регулярное выражение выиграло независимо от того, сколько итераций я выполнял.
Между прочим, кэширование, которое фреймворк выполняет при использовании статических методов Regex, является оптимизацией, которая необходима только при использовании статических методов Regex. Это связано с тем, что каждый вызов статического метода Regex создает новый объект Regex. В конструкторе класса Regex он должен проанализировать шаблон. Кэширование позволяет при последующих вызовах статических методов Regex повторно использовать RegexTree, проанализированный с первого вызова, тем самым избегая этапа синтаксического анализа.
Когда вы используете методы экземпляра для одного объекта Regex, это не проблема. Анализ по-прежнему выполняется только один раз (при создании объекта). Кроме того, вы можете избежать запуска всего остального кода в конструкторе, а также выделения кучи (и последующей сборки мусора).
Мартин Браун заметил, что вы перевернули аргументы своего статического вызова Regex (хороший улов, Мартин). Я думаю, вы обнаружите, что если вы это исправите, экземплярное (не скомпилированное) регулярное выражение будет каждый раз побеждать статические вызовы. Вы также должны обнаружить, что, учитывая мои выводы выше, скомпилированный экземпляр также превосходит некомпилированный.
НО: вы действительно должны прочитать Сообщение Джеффа Этвуда о скомпилированных регулярных выражениях, прежде чем слепо применять эту опцию к каждому создаваемому вами регулярному выражению.
Если вы постоянно сопоставляете одну и ту же строку с использованием одного и того же шаблона, это может объяснить, почему кешированная версия немного быстрее, чем скомпилированная версия.
В версии Regex.Match вы ищете ввод в шаблоне. Попробуйте поменять местами параметры.
var m3 = Regex.Match(pattern, item); // Wrong
var m3 = Regex.Match(item, pattern); // Correct
Ах. :-) Спасибо. Я обновлю свой пост с новыми результатами через минуту.
Это из документации;
https://msdn.microsoft.com/en-us/library/gg578045(v=vs.110).aspx
when a static regular expression method is called and the regular expression cannot be found in the cache, the regular expression engine converts the regular expression to a set of operation codes and stores them in the cache. It then converts these operation codes to MSIL so that the JIT compiler can execute them. Interpreted regular expressions reduce startup time at the cost of slower execution time. Because of this, they are best used when the regular expression is used in a small number of method calls, or if the exact number of calls to regular expression methods is unknown but is expected to be small. As the number of method calls increases, the performance gain from reduced startup time is outstripped by the slower execution speed.
In contrast to interpreted regular expressions, compiled regular expressions increase startup time but execute individual pattern-matching methods faster. As a result, the performance benefit that results from compiling the regular expression increases in proportion to the number of regular expression methods called.
To summarize, we recommend that you use interpreted regular expressions when you call regular expression methods with a specific regular expression relatively infrequently.
You should use compiled regular expressions when you call regular expression methods with a specific regular expression relatively frequently.
Как обнаружить?
The exact threshold at which the slower execution speeds of interpreted regular expressions outweigh gains from their reduced startup time, or the threshold at which the slower startup times of compiled regular expressions outweigh gains from their faster execution speeds, is difficult to determine. It depends on a variety of factors, including the complexity of the regular expression and the specific data that it processes. To determine whether interpreted or compiled regular expressions offer the best performance for your particular application scenario, you can use the Stopwatch class to compare their execution times.
Скомпилированные регулярные выражения:
We recommend that you compile regular expressions to an assembly in the following situations:
- If you are a component developer who wants to create a library of reusable regular expressions.
- If you expect your regular expression's pattern-matching methods to be called an indeterminate number of times -- anywhere from once or twice to thousands or tens of thousands of times. Unlike compiled or interpreted regular expressions, regular expressions that are compiled to separate assemblies offer performance that is consistent regardless of the number of method calls.
Спасибо за ваши объяснения. В моем случае первый шаг не требует больших затрат (см. Новые результаты). Я прочитал сообщение Джеффа Этвуда перед тем, как опубликовать это. Так что я в курсе минусов. В моем случае опция Compile могла бы помочь, хотя и не так сильно в стандартном варианте использования.