Ответ 1
RegexOptions.Compiled
инструктирует механизм регулярных выражений скомпилировать выражение регулярного выражения в IL с использованием генерации легкого кода (LCG). Эта компиляция происходит во время построения объекта, а сильно замедляет работу. В свою очередь, совпадения с использованием регулярного выражения выполняются быстрее.
Если вы не укажете этот флаг, ваше регулярное выражение считается "интерпретированным".
Возьмем этот пример:
public static void TimeAction(string description, int times, Action func)
{
// warmup
func();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < times; i++)
{
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
static void Main(string[] args)
{
var simple = "^\\d+$";
var medium = @"^((to|from)\W)?(?<url>http://[\w\.:]+)/questions/(?<questionId>\d+)(/(\w|-)*)?(/(?<answerId>\d+))?";
var complex = @"^(([^<>()[\]\\.,;:\[email protected]""]+"
+ @"(\.[^<>()[\]\\.,;:\[email protected]""]+)*)|("".+""))@"
+ @"((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
+ @"\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+"
+ @"[a-zA-Z]{2,}))$";
string[] numbers = new string[] {"1","two", "8378373", "38737", "3873783z"};
string[] emails = new string[] { "[email protected]", "[email protected]", "[email protected]", "[email protected]" };
foreach (var item in new[] {
new {Pattern = simple, Matches = numbers, Name = "Simple number match"},
new {Pattern = medium, Matches = emails, Name = "Simple email match"},
new {Pattern = complex, Matches = emails, Name = "Complex email match"}
})
{
int i = 0;
Regex regex;
TimeAction(item.Name + " interpreted uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
i = 0;
TimeAction(item.Name + " compiled uncached single match (x1000)", 1000, () =>
{
regex = new Regex(item.Pattern, RegexOptions.Compiled);
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern);
i = 0;
TimeAction(item.Name + " prepared interpreted match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
regex = new Regex(item.Pattern, RegexOptions.Compiled);
i = 0;
TimeAction(item.Name + " prepared compiled match (x1000000)", 1000000, () =>
{
regex.Match(item.Matches[i++ % item.Matches.Length]);
});
}
}
Он выполняет 4 теста на 3 разных регулярных выражениях. Сначала он тестирует одиночное однократное совпадение (скомпилировано vs non compiled). Во-вторых, он проверяет повторяющиеся совпадения, которые повторно используют одно и то же регулярное выражение.
Результаты на моей машине (скомпилированные в версии, без отладчика)
1000 одиночных совпадений (построить Regex, Match и dispose)
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 4 ms | 26 ms | 31 ms Interpreted | x64 | 5 ms | 29 ms | 35 ms Compiled | x86 | 913 ms | 3775 ms | 4487 ms Compiled | x64 | 3300 ms | 21985 ms | 22793 ms
1 000 000 совпадений - повторное использование объекта Regex
Type | Platform | Trivial Number | Simple Email Check | Ext Email Check ------------------------------------------------------------------------------ Interpreted | x86 | 422 ms | 461 ms | 2122 ms Interpreted | x64 | 436 ms | 463 ms | 2167 ms Compiled | x86 | 279 ms | 166 ms | 1268 ms Compiled | x64 | 281 ms | 176 ms | 1180 ms
Эти результаты показывают, что скомпилированные регулярные выражения могут быть до 60% быстрее для случаев, когда вы повторно используете объект Regex
. Однако в некоторых случаях может быть более на 3 порядка медленнее.
Он также показывает, что x64 версия для .NET может быть в 5-6 раз медленнее, когда дело доходит до компиляции регулярных выражений.
Рекомендация заключалась бы в использовании скомпилированной версии в тех случаях, когда
- Вы не заботитесь об стоимости инициализации объекта и нуждаетесь в дополнительном повышении производительности. (обратите внимание, что мы говорим о долях миллисекунды здесь).
- Вы немного заботитесь об стоимости инициализации, но повторно используете объект Regex столько раз, что он будет компенсировать его во время жизненного цикла приложения.
Гаечный ключ в работе, кеш регулярных выражений
Механизм регулярных выражений содержит кеш LRU, который содержит последние 15 регулярных выражений, которые были протестированы с использованием статических методов класса Regex
.
Например: Regex.Replace
, Regex.Match
и т.д. все используют кэш Regex.
Размер кеша можно увеличить, установив Regex.CacheSize
. Он принимает изменения в размере в любое время в течение жизненного цикла приложения.
Новые регулярные выражения кэшируются статическими помощниками в классе Regex. Если вы построите свои объекты, кеш будет проверен (для повторного использования и наброска), однако созданное вами регулярное выражение не добавлено в кэш.
Этот кеш является тривиальным LRU кешем, он реализован с использованием простого двойного списка. Если вам удастся увеличить его до 5000 и использовать 5000 разных вызовов для статических помощников, каждая конструкция регулярных выражений будет обходить 5000 записей, чтобы увидеть, было ли они ранее кэшированы. Вокруг проверки есть блокировка, поэтому проверка может уменьшить parallelism и ввести блокировку потока.
Число установлено достаточно низким, чтобы защитить себя от таких случаев, хотя в некоторых случаях у вас может не быть выбора, кроме как увеличить его.
передатьRegexOptions.Compiled
статическому помощнику.
Например:
\\ WARNING: bad code
Regex.IsMatch(@"\\d+", "10000", RegexOptions.Compiled)
Причина заключается в том, что вы сильно рискуете пропустить кеш LRU, который вызовет компиляцию супердорогая. Кроме того, вы не представляете, чем занимаются библиотеки, потому что у вас мало возможностей контролировать или прогнозировать размер наилучшего кеша.
Смотрите также: Блог команды BCL
Примечание: это относится к .NET 2.0 и .NET 4.0. Есть некоторые ожидаемые изменения в 4.5, которые могут привести к его пересмотру.