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

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

156 голосов
/ 05 февраля 2009

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

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

Ответы [ 4 ]

282 голосов
/ 10 октября 2011

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 одиночных совпадений (создайте регулярное выражение, сопоставьте и утилизируйте)

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 версия. 1035 * .NET может быть в 5-6 раз медленнее , когда дело доходит до компиляции регулярных выражений.


Рекомендуется использовать скомпилированную версию в случаях, когда

  1. Вы не заботитесь о стоимости инициализации объекта и нуждаетесь в дополнительном повышении производительности. (заметьте, мы говорим здесь доли миллисекунды)
  2. Вы немного заботитесь о стоимости инициализации, но повторно используете объект Regex так много раз, что он компенсирует его в течение жизненного цикла вашего приложения.

гаечный ключ в работах, кеш Regex

Механизм регулярных выражений содержит кэш LRU, в котором хранятся последние 15 регулярных выражений, которые были протестированы с использованием статических методов в классе Regex.

Например: Regex.Replace, Regex.Match и т.д .. все используют кеш Regex.

Размер кэша можно увеличить, установив Regex.CacheSize. Он принимает изменения в размере в любое время в течение жизненного цикла вашего приложения.

Новые регулярные выражения кэшируются только статическими помощниками в классе Regex. Если вы создаете свои объекты, кэш проверяется (для повторного использования и увеличения), однако, регулярное выражение, которое вы создаете, не добавляется в кеш .

Этот кеш является тривиальным LRU-кешем, он реализован с использованием простого двойного связанного списка. Если вам доведется увеличить его до 5000 и использовать 5000 различных вызовов статических помощников, каждая конструкция регулярного выражения будет сканировать 5000 записей, чтобы увидеть, было ли оно ранее кэшировано. Вокруг чека есть lock , поэтому чек может уменьшить параллелизм и вызвать блокировку потоков.

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

Моя сильная рекомендация будет никогда передать параметр RegexOptions.Compiled статическому помощнику.

Например:

\\ WARNING: bad code
Regex.IsMatch("10000", @"\\d+", RegexOptions.Compiled)

Причина в том, что вы сильно рискуете пропустить кеш LRU, что вызовет супердорогую компиляцию. Кроме того, вы понятия не имеете, что делают библиотеки, от которых вы зависите, поэтому не имеете возможности контролировать или прогнозировать наилучший из возможных размер кэша.

Смотрите также: Блог команды BCL


Примечание : это относится к .NET 2.0 и .NET 4.0. В 4.5 ожидаются некоторые изменения, которые могут привести к его пересмотру.

40 голосов
/ 05 февраля 2009

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

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

  1. 1010 * интерпретирован *

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

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

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

  3. прекомпилируются

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

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

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

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

10 голосов
/ 05 февраля 2009

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

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

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

Ссылка: BCL Team Blog Производительность регулярных выражений [Дэвид Гутьеррес]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...