Як працює RegexOptions.Compiled?


169

Що відбувається за кадром, коли ви позначаєте регулярний вираз як один, який слід скласти? Чим це порівняння / відрізняється від кешованого регулярного виразу?

Використовуючи цю інформацію, як визначити, коли вартість обчислень незначна порівняно зі збільшенням продуктивності?


хороший ресурс щодо найкращих практик Regex: docs.microsoft.com/en-us/dotnet/standard/base-types/…
CAD

Відповіді:


302

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 разів повільніше, коли справа стосується компіляції регулярних виразів.


Рекомендацією є використання компільованої версії у випадках, коли будь-яка

  1. Вам не байдуже вартість ініціалізації об’єктів і вам потрібно додаткове підвищення продуктивності. (зауважте, ми говоримо про частки мілісекунди тут)
  2. Ви трохи піклуєтесь про вартість ініціалізації, але повторно використовуєте об'єкт 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 можуть призвести до перегляду цього.


11
Чудова відповідь. Для своїх цілей я часто використовую Compiledв коді веб-сайту, де я фактично зберігаю статичний об’єкт (для програми) Regex. Тож Regexєдине має бути побудовано один раз, коли IIS запускає програму, а потім повторно використовується тисячі разів. Це добре працює, доки програма не перезапускається часто.
Стів Вортем

W00! Ця інформація допомогла мені прискорити процес з 8-13 годин до ~ 30 хвилин. Дякую!
Роберт Христос

3
Чудова відповідь Сем, чи можете ви поновити інформацію про те, що змінилося у версії> 4.5? (Я знаю, що ти змінив свій стек назад ...)
gdoron підтримує Моніку

@gdoronissupportingMonica У NET 5.0 було покращено продуктивність Regex. Я побачив повідомлення про це в блозі. Перевірити це можна тут
kapozade

42

Цей запис у Блозі команд BCL дає хороший огляд: " Виконання регулярних виразів ".

Коротше кажучи, є три типи регулярних виразів (кожен виконується швидше, ніж попередній):

  1. інтерпретується

    швидко створювати на ходу, повільно виконувати

  2. складений (той, про який ви, здається, запитуєте)

    повільніше створювати на ходу, швидко виконувати (добре для виконання у циклах)

  3. попередньо складений

    створювати під час компіляції програми (без штрафу за час виконання), швидко виконувати

Отже, якщо ви збираєтесь виконати регулярний вираз лише один раз або в критичному розділі свого додатка (наприклад, перевірка введення користувачем), у вас є варіант 1.

Якщо ви маєте намір запустити регулярний вираз у циклі (тобто рядковому розборі файлу), слід перейти до параметра 2.

Якщо у вас є багато регексів, які ніколи не змінюватимуться для вашої програми та використовуються інтенсивно, ви можете скористатися варіантом 3.



9

Слід зазначити, що продуктивність регулярних виразів з моменту .NET 2.0 була покращена за допомогою кешу MRU неукомпільованих регулярних виразів. Код бібліотеки Regex більше не повторює кожен і той самий некомпільований регулярний вираз кожен раз.

Таким чином, потенційно існує більший штраф за виконання, зі складеним регулярним виразом. Крім уповільнення часу завантаження, система також використовує більше пам’яті для складання регулярного виразу в опкоди.

По суті, поточні поради або не складають регулярні вирази, або складають їх заздалегідь до окремої збірки.

Ref: Блог команд BCL Регулярні вислови [David Gutierrez]



0

Я сподіваюся, що наведений нижче код допоможе вам зрозуміти поняття функцій re.compile

import re

x="""101 COM    Computers
205 MAT   Mathematics
189 ENG   English
222 SCI Science
333 TA  Tamil
5555 KA  Kannada
6666  TL  Telugu
777777 FR French
"""

#compile reg expression / successfully compiled regex can be used in any regex 
#functions    
find_subject_code=re.compile("\d+",re.M)
#using compiled regex in regex function way - 1
out=find_subject_code.findall(x)
print(out)
#using compiled regex in regex function way - 2
out=re.findall(find_numbers,x)
print(out)

#few more eg:
#find subject name
find_subjectnames=re.compile("(\w+$)",re.M) 
out=find_subjectnames.findall(x)
print(out)


#find subject SHORT name
find_subject_short_names=re.compile("[A-Z]{2,3}",re.M) 
out=find_subject_short_names.findall(x)
print(out)

Дякуємо за вашу відповідь, але ваш код є мовою Python . Питання полягало у варіанті Microsoft .NET Framework RegexOptions.Compiled . Ви можете бачити тег [ .net ], що додається нижче питання.
стом

ой так! Дякую стоми
Даніель Мутупанді
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.