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 = @"^(([^<>()[\]\\.,;:\s@""]+"
+ @"(\.[^<>()[\]\\.,;:\s@""]+)*)|("".+""))@"
+ @"((\[[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[] { "sam@sam.com", "sss@s", "sjg@ddd.com.au.au", "onelongemail@oneverylongemail.com" };
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 різних регулярних виразах. Спочатку він тестує одиночний одноразовий збіг (компільований проти некомпільований). По-друге, він тестує повторення відповідностей, які використовують повторно те саме регулярне вираження.
Результати на моїй машині (зібрано у випуску, не налагоджено налагодження)
1000 одиночних сірників (сконструюйте Regex, Match and dispose)
Тип | Платформа | Тривіальне число | Проста перевірка електронної пошти | Зовнішня перевірка електронної пошти
-------------------------------------------------- ----------------------------
Інтерпретована | x86 | 4 мс | 26 мс | 31 мс
Інтерпретована | x64 | 5 мс | 29 мс | 35 мс
Складено | x86 | 913 мс | 3775 мс | 4487 мс
Складено | x64 | 3300 мс | 21985 мс | 22793 мс
1 000 000 матчів - повторне використання об'єкта Regex
Тип | Платформа | Тривіальне число | Проста перевірка електронної пошти | Зовнішня перевірка електронної пошти
-------------------------------------------------- ----------------------------
Інтерпретована | x86 | 422 мс | 461 мс | 2122 мс
Інтерпретована | x64 | 436 мс | 463 мс | 2167 мс
Складено | x86 | 279 мс | 166 мс | 1268 мс
Складено | x64 | 281 мс | 176 мс | 1180 мс
Ці результати показують, що складені регулярні вирази можуть бути до 60% швидшими у випадках, коли ви повторно використовуєте Regexоб'єкт. Однак у деяких випадках побудувати на 3 порядки повільніше.
Це також показує, що версія NET x64 може бути в 5 - 6 разів повільніше, коли справа стосується компіляції регулярних виразів.
Рекомендацією є використання компільованої версії у випадках, коли будь-яка
- Вам не байдуже вартість ініціалізації об’єктів і вам потрібно додаткове підвищення продуктивності. (зауважте, ми говоримо про частки мілісекунди тут)
- Ви трохи піклуєтесь про вартість ініціалізації, але повторно використовуєте об'єкт Regex стільки разів, що він компенсує його протягом життєвого циклу вашої програми.
Ключ у творах, кеш Regex
Двигун регулярних виразів містить кеш LRU, який містить 15 останніх регулярних виразів, протестованих за допомогою статичних методів у Regexкласі.
Наприклад: Regex.Replaceі Regex.Matchт. Д. Всі використовують кеш Regex.
Розмір кешу можна збільшити, встановивши Regex.CacheSize. Він приймає зміни розміру в будь-який час протягом життєвого циклу вашої програми.
Нові регулярні вирази кешуються лише статичними помічниками класу Regex. Якщо ви конструюєте свої об'єкти, кеш перевіряється (для повторного використання та зіткнення), однак регулярний вираз, який ви створюєте, не додається до кеша .
Цей кеш є тривіальним кешем LRU, він реалізований за допомогою простого подвійного зв'язаного списку. Якщо ви збільшите її до 5000 і використовуєте 5000 різних викликів статичних помічників, кожна конструкція регулярного виразу сканує 5000 записів, щоб побачити, чи вона раніше була кешована. Навколо чека є фіксатор , тому перевірка може зменшити паралелізм та запровадити блокування потоку.
Кількість встановлюється досить низькою, щоб захистити себе від подібних справ, хоча в деяких випадках у вас не залишається іншого вибору, як збільшити її.
Моя наполеглива рекомендація НЕ буде ніколи передати RegexOptions.Compiledопцію статичного помічника.
Наприклад:
\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)
Причина полягає в тому, що ви сильно ризикуєте пропустити кеш LRU, що спровокує надто дорогу компіляцію. Крім того, ви не маєте уявлення про те, від чого залежать бібліотеки, від яких ви залежите, тому не маєте можливості контролювати або передбачати найкращий можливий розмір кешу.
Дивіться також: Блог команди BCL
Примітка . Це стосується .NET 2.0 та .NET 4.0. Очікувані зміни в 4.5 можуть призвести до перегляду цього.