Как работает RegexOptions.Compiled?

169

Что происходит за кулисами, когда вы помечаете регулярное выражение как компилируемое? Чем это отличается / отличается от кэшированного регулярного выражения?

Используя эту информацию, как вы определяете, когда стоимость вычислений незначительна по сравнению с увеличением производительности?

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

Ответы:

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 и dispose)

Тип | Платформа | Тривиальный номер | Простая проверка электронной почты | Ext Email Check
-------------------------------------------------- ----------------------------
Интерпретированный | х86 | 4 мс | 26 мс | 31 мс
Интерпретированный | х64 | 5 мс | 29 мс | 35 мс
Скомпилировано | х86 | 913 мс | 3775 мс | 4487 мс
Скомпилировано | х64 | 3300 мс | 21985 мс | 22793 мс

1 000 000 совпадений - повторное использование объекта Regex

Тип | Платформа | Тривиальный номер | Простая проверка электронной почты | Ext Email Check
-------------------------------------------------- ----------------------------
Интерпретированный | х86 | 422 мс | 461 мс | 2122 мс
Интерпретированный | х64 | 436 мс | 463 мс | 2167 мс
Скомпилировано | х86 | 279 мс | 166 мс | 1268 мс
Скомпилировано | х64 | 281 мс | 176 мс | 1180 мс

Эти результаты показывают, что скомпилированные регулярные выражения могут быть на 60% быстрее в случаях, когда вы повторно используете Regexобъект. Однако в некоторых случаях его можно построить на 3 порядка медленнее.

Это также показывает, что x64-версия .NET может быть в 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? (Я знаю, что вы изменили свой стек некоторое время назад ...)
Гдорон поддерживает Монику
@gdoronissupportingMonica В NET 5.0 произошли некоторые улучшения производительности Regex. Я видел сообщение в блоге для этого. Вы можете проверить это здесь
Kapozade
42

Эта запись в блоге команды BCL дает хороший обзор: « Производительность регулярных выражений ».

Короче говоря, существует три типа регулярных выражений (каждый выполняется быстрее, чем предыдущий):

  1. интерпретированы

    быстро создавать на лету, медленно выполнять

  2. скомпилирован (тот, о котором вы, кажется, спрашиваете)

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

  3. предварительно скомпилированных

    создавать во время компиляции вашего приложения (без штрафов за создание во время выполнения), быстро выполнить

Таким образом, если вы намереваетесь выполнить регулярное выражение только один раз или в не критически важном для производительности разделе вашего приложения (т. Е. Проверка пользовательского ввода), у вас все в порядке с вариантом 1.

Если вы намереваетесь запустить регулярное выражение в цикле (т. Е. Построчный анализ файла), вам следует перейти к варианту 2.

Если у вас есть много регулярных выражений, которые никогда не изменятся для вашего приложения и интенсивно используются, вы можете выбрать вариант 3.

Томалак
источник
1
Номер 3 можно легко сделать с помощью обычного рослинаCompileModule . Черт, мне нужно глубже взглянуть на новую платформу.
Кристиан Голлхардт
9

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

Так что потенциально больше производительности штраф с обобщаются и на лету регулярного выражения. В дополнение к более медленным временам загрузки система также использует больше памяти для компиляции регулярного выражения в коды операций.

По сути, текущий совет: либо не компилируйте регулярные выражения, либо скомпилируйте их заранее в отдельную сборку.

Ссылка: BCL Team Blog Производительность регулярных выражений [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 рамочного RegexOptions.Compiled вариант. Вы можете увидеть тег [ .net ], прикрепленный под вопросом.
19
о да Спасибо, stomy
Даниэль Мутупанди