Подтвердить что ты не робот

Почему скомпилированная производительность RegEx медленнее, чем Intrepreted RegEx?

Я столкнулся с этой статьей:

Производительность: скомпилированные или интерпретированные регулярные выражения, я модифицировал образец кода для компиляции 1000 Regex, а затем запускаю каждые 500 раз, чтобы воспользоваться преимуществами прекомпиляции, однако даже в этом случае интерпретируемые RegExes выполняются в 4 раза быстрее!

Это означает, что параметр RegexOptions.Compiled полностью бесполезен, а тем более хуже - медленнее! Большая разница была связана с JIT, после решения JIT скомпилированного регулярного выражения в следующем коде все еще выполняется немного медленнее и не имеет смысла для меня, но @Jim в ответах предоставил гораздо более чистую версию, которая работает как ожидается.

Может кто-нибудь объяснить, почему это так?

Код, принятый и измененный из сообщения в блоге:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace RegExTester
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;

            for (int i = 0; i < 1000; i++)
            {
                CheckForMatches("some random text with email address, [email protected]" + i.ToString());    
            }


            double msTaken = DateTime.Now.Subtract(startTime).TotalMilliseconds;
            Console.WriteLine("Full Run: " + msTaken);


            startTime = DateTime.Now;

            for (int i = 0; i < 1000; i++)
            {
                CheckForMatches("some random text with email address, [email protected]" + i.ToString());
            }


            msTaken = DateTime.Now.Subtract(startTime).TotalMilliseconds;
            Console.WriteLine("Full Run: " + msTaken);

            Console.ReadLine();

        }


        private static List<Regex> _expressions;
        private static object _SyncRoot = new object();

        private static List<Regex> GetExpressions()
        {
            if (_expressions != null)
                return _expressions;

            lock (_SyncRoot)
            {
                if (_expressions == null)
                {
                    DateTime startTime = DateTime.Now;

                    List<Regex> tempExpressions = new List<Regex>();
                    string regExPattern =
                        @"^[a-zA-Z0-9]+[a-zA-Z0-9._%-]*@{0}$";

                    for (int i = 0; i < 2000; i++)
                    {
                        tempExpressions.Add(new Regex(
                            string.Format(regExPattern,
                            Regex.Escape("domain" + i.ToString() + "." +
                            (i % 3 == 0 ? ".com" : ".net"))),
                            RegexOptions.IgnoreCase));//  | RegexOptions.Compiled
                    }

                    _expressions = new List<Regex>(tempExpressions);
                    DateTime endTime = DateTime.Now;
                    double msTaken = endTime.Subtract(startTime).TotalMilliseconds;
                    Console.WriteLine("Init:" + msTaken);
                }
            }

            return _expressions;
        }

        static  List<Regex> expressions = GetExpressions();

        private static void CheckForMatches(string text)
        {

            DateTime startTime = DateTime.Now;


                foreach (Regex e in expressions)
                {
                    bool isMatch = e.IsMatch(text);
                }


            DateTime endTime = DateTime.Now;
            //double msTaken = endTime.Subtract(startTime).TotalMilliseconds;
            //Console.WriteLine("Run: " + msTaken);

        }
    }
}
4b9b3361

Ответ 1

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

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

    const int NumIterations = 1000;
    const string TestString = "some random text with email address, [email protected]";
    const string Pattern = "^[a-zA-Z0-9]+[a-zA-Z0-9._%-]*@domain0\\.\\.com$";
    private static Regex NormalRegex = new Regex(Pattern, RegexOptions.IgnoreCase);
    private static Regex CompiledRegex = new Regex(Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled);
    private static Regex DummyRegex = new Regex("^.$");

    static void Main(string[] args)
    {
        var DoTest = new Action<string, Regex, int>((s, r, count) =>
            {
                Console.Write("Testing {0} ... ", s);
                Stopwatch sw = Stopwatch.StartNew();
                for (int i = 0; i < count; ++i)
                {
                    bool isMatch = r.IsMatch(TestString + i.ToString());
                }
                sw.Stop();
                Console.WriteLine("{0:N0} ms", sw.ElapsedMilliseconds);
            });

        // Make sure that DoTest is JITed
        DoTest("Dummy", DummyRegex, 1);
        DoTest("Normal first time", NormalRegex, 1);
        DoTest("Normal Regex", NormalRegex, NumIterations);
        DoTest("Compiled first time", CompiledRegex, 1);
        DoTest("Compiled", CompiledRegex, NumIterations);

        Console.WriteLine();
        Console.Write("Done. Press Enter:");
        Console.ReadLine();
    }

Настройка NumIterations до 500 дает мне следующее:

Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 1 ms
Testing Compiled first time ... 13 ms
Testing Compiled ... 1 ms

С 5 миллионами итераций я получаю:

Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 17,232 ms
Testing Compiled first time ... 17 ms
Testing Compiled ... 15,299 ms

Здесь вы видите, что скомпилированное регулярное выражение не менее чем на 10% быстрее, чем не скомпилированная версия.

Интересно отметить, что если вы удалите RegexOptions.IgnoreCase из своего регулярного выражения, результаты из 5 миллионов итераций еще более поразительны:

Testing Dummy ... 0 ms
Testing Normal first time ... 0 ms
Testing Normal Regex ... 12,869 ms
Testing Compiled first time ... 14 ms
Testing Compiled ... 8,332 ms

Здесь скомпилированное регулярное выражение на 35% быстрее, чем не скомпилированное регулярное выражение.

На мой взгляд, сообщение в блоге, на котором вы ссылаетесь, является просто ошибочным тестом.

Ответ 2

http://www.codinghorror.com/blog/2005/03/to-compile-or-not-to-compile.html

Скомпилировано помогает только в том случае, если вы создаете экземпляр его один раз и повторно используете его несколько раз. Если вы создаете скомпилированное регулярное выражение в цикле for, то он, очевидно, будет работать хуже. Можете ли вы показать нам свой пример кода?

Ответ 3

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

Сценарий, в котором скомпилирован Regex был разработан (я считаю, что я его не проектировал), имеет десятки Regexes, выполненных миллионы раз, а не тысячи Regexes, выполняемых тысячи раз. Если вы не собираетесь выполнять Regex в области миллион раз, вы, вероятно, даже не воспользуетесь временем, чтобы JIT скомпилировать его.

Ответ 4

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

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

  • Этот код не учитывает затраты JIT метода. Он должен запустить код один раз, чтобы получить затраты на JIT, а затем запустить его снова и измерить
  • Почему lock используется вообще? Это совершенно необязательно
  • В тестах следует использовать StopWatch not DateTime
  • Чтобы получить хорошее сравнение между скомпилированными и не скомпилированными, вы должны проверить производительность одного скомпилированного Regex и одного некомпилированного Regex, совпадающего с N раз. Не N каждого совпадения не более одного раза в регулярном выражении.