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

Когда не использовать RegexOptions.Compiled

Я понимаю преимущество использования RegexOptions.Compiled - он улучшает время выполнения приложения, используя регулярное выражение в скомпилированной форме вместо того, чтобы интерпретировать его во время выполнения. Хотя использование этого не рекомендуется для приложений, которые уже не работают во время запуска.

Но если мое приложение может немного увеличить время запуска -
каковы другие сценарии, в которых я НЕ должен использовать RegexOptions.Compiled?

Как примечание, я вызываю этот метод несколько раз -

private static string GetName(string objString)
{
    return Regex.Replace(objString, "[^a-zA-Z&-]+", "");
}

Итак, этот метод вызывается с разными значениями для 'objString' (хотя значения для objString также могут повторяться).

Считаете ли вы, что это хорошо/не хорошо использовать RegexOptions.Compiled здесь? Любая веб-ссылка будет действительно полезна.
Спасибо!


ИЗМЕНИТЬ

Я тестировал свое веб-приложение с помощью

  • RegexOptions.Compiled и
  • Выполнить Regex как переменную класса

Но не удалось найти большой разницы во времени, затрачиваемом моим веб-приложением - Единственное, что я заметил в обоих сценариях, это то, что в первый раз, когда приложение загружает его, в два раза больше времени, чем при последовательных загрузках страниц, и это независимо от того, использую ли я RegexOptions.Compiled или нет.

Любые комментарии для -
почему мое веб-приложение занимает больше времени для обработки Regex для первого раза и времени, которое сокращается почти до половины или меньше при последующих нагрузках. Есть ли встроенное кэширование или какая-то другая функция .net здесь. Постскриптум Эта вещь такая же, если я использую RegexOptions.Compiled или нет.

4b9b3361

Ответ 1

Для любого конкретного вопроса производительности, подобного этому, лучший способ узнать, какой путь быстрее, - проверить и то, и другое.

В общем случае компиляция регулярного выражения вряд ли принесет много пользы, если вы не используете регулярное выражение много или в очень больших строках. (Или и то, и другое.) Я думаю, что это скорее оптимизация, чтобы попытаться после того, как вы определили, что у вас проблема с производительностью, и вы думаете, что это может помочь, чем попытка случайного опроса.

Для некоторого общего обсуждения недостатков RegexOptions.Compiled см. это сообщение в блоге Джеффа Этвуда; он очень старый, но из того, что я понимаю, ни один из основных важных фактов не изменился с момента его написания.

Ответ 2

Две вещи, о которых стоит подумать, - это то, что RegexOptions.Compiled занимает процессорное время и память.

Имея это в виду, существует только один экземпляр, когда вы должны не использовать RegexOptions.Compiled:

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

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

Теперь, если вы выберете RegexOptions.Compiled, важно, чтобы вы не расточились ему.

Часто лучший способ сделать это - определить свой объект как статическую переменную, которую можно повторно использовать много раз. Например...

public static Regex NameRegex = new Regex(@"[^a-zA-Z&-]+", RegexOptions.Compiled);

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

Итак, в этом случае это будет что-то вроде этого...

public static Lazy<Regex> NameRegex = 
    new Lazy<Regex>(() => new Regex("[^a-zA-Z&-]+", RegexOptions.Compiled));

Затем вы просто ссылаетесь на NameRegex.Value всякий раз, когда хотите использовать это регулярное выражение, и оно создается только при первом доступе.


RegexOptions.Compiled в реальном мире

На нескольких моих сайтах я использую маршруты Regex для ASP.NET MVC. И этот сценарий идеально подходит для RegexOptions.Compiled. Маршруты определяются при запуске веб-приложения и затем повторно используются для всех последующих запросов, пока приложение продолжает работать. Таким образом, эти регулярные выражения создаются и компилируются один раз и повторно используются миллионы раз.

Ответ 3

Из сообщения в блоге BCL, компиляция увеличивает время запуска на порядок, но уменьшает последующие промежутки времени примерно на 30%. Используя эти числа, компиляцию следует рассматривать для шаблона, который вы ожидаете оценить более чем в 30 раз. (Конечно, как и любая оптимизация производительности, обе альтернативы должны быть измерены для приемлемости.)

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

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

    static string GetName1(string objString)
    {
        return Regex.Replace(objString, "[^a-zA-Z&-]+", "");
    }

    static string GetName2(string objString)
    {
        return Regex.Replace(objString, "[^a-zA-Z&-]+", "", RegexOptions.Compiled);
    }

    static string GetName3(string objString)
    {
        var sb = new StringBuilder(objString.Length);
        foreach (char c in objString)
            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '-' || c == '&')
                sb.Append(c);
        return sb.ToString();
    }


    static string GetName4(string objString)
    {
        char[] c = objString.ToCharArray();
        int pos = 0;
        int writ = 0;
        while (pos < c.Length)
        {
            char curr = c[pos];
            if ((curr >= 'A' && curr <= 'Z') || (curr >= 'a' && curr <= 'z') || curr == '-' || curr == '&')
            {
                c[writ++] = c[pos];
            }
            pos++;
        }
        return new string(c, 0, writ);
    }


    unsafe static string GetName5(string objString)
    {
        char* buf = stackalloc char[objString.Length];
        int writ = 0;
        fixed (char* sp = objString)
        {
            char* pos = sp;
            while (*pos != '\0')
            {
                char curr = *pos;
                if ((curr >= 'A' && curr <= 'Z') ||
                    (curr >= 'a' && curr <= 'z') ||
                     curr == '-' || curr == '&')
                    buf[writ++] = curr;
                pos++;
            }
        }
        return new string(buf, 0, writ);
    }

Выполнение независимо для 5 миллионов случайных строк ASCII, по 30 символов, последовательно выдавало эти числа:

   Method 1: 32.3  seconds (interpreted regex)
   Method 2: 24.4  seconds (compiled regex)
   Method 3:  1.82 seconds (StringBuilder concatenation)
   Method 4:  1.64 seconds (char[] manipulation)
   Method 5:  1.54 seconds (unsafe char* manipulation)

То есть, компиляция обеспечила 25% -ное преимущество в производительности для очень большого количества оценок этого шаблона, при этом первое выполнение было примерно в 3 раза медленнее. Методы, которые работали на базовых массивах символов, были в 12 раз быстрее, чем скомпилированные регулярные выражения.

В то время как метод 4 или метод 5 могут обеспечить некоторое преимущество в производительности по сравнению с регулярными выражениями, другие методы могут обеспечить другие преимущества (ремонтопригодность, читаемость, гибкость и т.д.). Этот простой тест позволяет предположить, что в этом случае компиляция регулярного выражения имеет скромное преимущество в производительности за интерпретацию его для большого количества оценок.

Ответ 4

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

Возможно, вы захотите перестроить код таким образом (обратите внимание, что я повторно написал регулярное выражение так, как вам кажется. Наличие старта-карата в повторяющейся группе не имеет большого смысла, и Я предполагаю, что префикс имени заканчивается тире):

    private static readonly Regex CompiledRegex = new Regex("^[a-zA-Z]+-", RegexOptions.Compiled);
    private static string GetNameCompiled(string objString)
    {
        return CompiledRegex.Replace(objString, "");
    }

Я также написал для этого тестовый код:

    public static void TestSpeed()
    {
        var testData = "fooooo-bar";
        var timer = new Stopwatch();

        timer.Start();
        for (var i = 0; i < 10000; i++)
            Assert.AreEqual("bar", GetNameCompiled(testData));
        timer.Stop();
        Console.WriteLine("Compiled took " + timer.ElapsedMilliseconds + "ms");
        timer.Reset();

        timer.Start();
        for (var i = 0; i < 10000; i++)
            Assert.AreEqual("bar", GetName(testData));
        timer.Stop();
        Console.WriteLine("Uncompiled took " + timer.ElapsedMilliseconds + "ms");
        timer.Reset();

    }

    private static readonly Regex CompiledRegex = new Regex("^[a-zA-Z]+-", RegexOptions.Compiled);
    private static string GetNameCompiled(string objString)
    {
        return CompiledRegex.Replace(objString, "");
    }

    private static string GetName(string objString)
    {
        return Regex.Replace(objString, "^[a-zA-Z]+-", "");
    }

На моей машине я получаю:

Скомпилированный взял 21 мс

Uncompiled взял 37 мс