Динамическая и встроенная производительность RegExp в JavaScript - PullRequest
30 голосов
/ 17 марта 2012

Я наткнулся на этот тест производительности, сказав, что RegExps в JavaScript не обязательно медленный: http://jsperf.com/regexp-indexof-perf

Есть одна вещь, которую я не понял: два случая связаны с чем-то, что я считаю абсолютно одинаковым:

RegExp('(?:^| )foo(?: |$)').test(node.className);

И

/(?:^| )foo(?: |$)/.test(node.className);

По-моему, эти две строки были точно одинаковыми, вторая - своего рода сокращение для создания объекта RegExp. Тем не менее, он в два раза быстрее , чем первый.

Эти случаи называются "динамическое регулярное выражение" и "встроенное регулярное выражение".

Может ли кто-нибудь помочь мне понять разницу (и разрыв в производительности) между этими двумя?

Ответы [ 3 ]

25 голосов
/ 11 сентября 2015

В настоящее время ответы, приведенные здесь, не являются полностью полными / правильными.

Начиная с ES5, буквальный синтаксис аналогичен синтаксису RegExp() в отношении создания объекта: они оба создают новый объект RegExp каждый раз, когда путь кода встречает выражение, в котором они принимают участие .

Следовательно, единственное различие между ними состоит в том, как часто это регулярное выражение компилируется :

  • С буквальным синтаксисом - один раз во время первоначального анализа кода и компиляции
  • С синтаксисом RegExp() - каждый раз, когда создается новый объект

См., Например, Шаблоны JavaScript Стояна Стефанова book:

Еще одно различие между литералом регулярного выражения и Конструктор состоит в том, что литерал создает объект только один раз за разбор времени. Если вы создаете одно и то же регулярное выражение в цикле, ранее созданный объект будет возвращен со всеми его свойствами (например, lastIndex) уже установлен с первого раза. Рассмотрим Следующий пример в качестве иллюстрации того же объекта вернулся дважды.

function getRE() {
    var re = /[a-z]/;
    re.foo = "bar";
    return re;
}

var reg = getRE(),
    re2 = getRE();

console.log(reg === re2); // true
reg.foo = "baz";
console.log(re2.foo); // "baz"

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

Если вы запустите этот пример во всех современных браузерах или NodeJS, вы получите следующее:

false
bar

Это означает, что e в то время, когда вы вызываете функцию getRE(), новый объект RegExp создается даже при использовании буквального синтаксиса .

Вышеприведенное не только объясняет, почему вы не должны использовать RegExp() для неизменяемых регулярных выражений (это очень известная проблема производительности сегодня), но также объясняет:

(меня больше удивляет, что inlineRegExp и storeRegExp имеют разные Результаты.)

В браузерах storedRegExp примерно на 5-20% быстрее, чем inlineRegExp, потому что нет никаких затрат на создание (и сбор мусора) нового объекта RegExp каждый раз.

Вывод:
Всегда создавайте свои неизменяемые регулярные выражения с буквальным синтаксисом и кэшируйте их, если они будут использоваться повторно. Другими словами, не полагайтесь на эту разницу в поведении в envs ниже ES5, и продолжайте надлежащим образом кэшировать в envs выше.

Почему буквальный синтаксис? Он имеет некоторые преимущества по сравнению с синтаксисом конструктора:

  1. Это короче и не заставляет вас думать с точки зрения классового Конструкторы.
  2. При использовании конструктора RegExp() вам также необходимо экранировать кавычки и обратные слэши с двойным экранированием. Делает регулярные выражения которые по своей природе трудно читать и понимать еще сложнее.

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

9 голосов
/ 04 ноября 2012

Разница в производительности не связана с используемым синтаксисом частично связана с используемым синтаксисом: в /pattern/ и RegExp(/pattern/) (где вы не проверяли последний) регулярное выражение компилируется только один раз, но для RegExp('pattern') выражение компилируется при каждом использовании. См. ответ Александра , который должен быть принятым ответом сегодня.

Помимо вышесказанного, в ваших тестах для inlineRegExp и storedRegExp вы смотрите на код, который инициализируется один раз при разборе текста исходного кода, в то время как для dynamicRegExp регулярное выражение создается для каждого вызова метода. Обратите внимание, что фактические тесты запускают такие вещи, как r = dynamicRegExp(element) много раз, в то время как код подготовки запускается только один раз.

Следующее дает примерно такие же результаты, в соответствии с другим jsPerf :

var reContains = /(?:^| )foo(?: |$)/;

... и

var reContains = RegExp('(?:^| )foo(?: |$)'); 

... когда оба используются с

function storedRegExp(node) {
  return reContains.test(node.className);
}

Конечно, исходный код RegExp('(?:^| )foo(?: |$)') может быть сначала проанализирован в String, а затем в RegExp, но я сомневаюсь, что он сам по себе будет в два раза медленнее. Однако следующее создаст новый RegExp(..) снова и снова для каждого вызова метода:

function dynamicRegExp(node) {
  return RegExp('(?:^| )foo(?: |$)').test(node.className);
}

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

(меня больше удивляет, что inlineRegExp и storedRegExp имеют разные результаты. Это объясняется в ответ Александра тоже.)

6 голосов
/ 17 марта 2012

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

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