Почему String.replace() с lambda медленнее, чем цикл while, повторно вызывающий RegExp.exec()?

Одна проблема:

Я хочу обработать строку (str), чтобы любые скобки (совпадающие с rgx) заменялись значениями, взятыми из соответствующего места в массиве (sub):

var rgx = /\((\d+)\)/,
    str = "this (0) a (1) sentence",
    sub = [
            "is",
            "test"
        ],
    result;

result, учитывая указанные выше переменные, должен быть "это тестовое предложение".

Два решения:

Это работает:

var mch,
    parsed = '',
    remainder = str;
while (mch = rgx.exec(remainder)) { // Not JSLint approved.
    parsed += remainder.substring(0, mch.index) + sub[mch[1]];
    remainder = remainder.substring(mch.index + mch[0].length);
}
result = (parsed) ? parsed + remainder : str;

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

result = str.replace(rgx, function() {
    return sub[arguments[1]];
});

Это тоже работает, но я ошибался в скорости; в Chrome это удивительно (~ 50%, последний раз я проверил) медленнее!

...

Три вопроса:

  • Почему этот процесс выглядит медленнее в Chrome и (например) быстрее в Firefox?
  • Есть ли вероятность того, что метод replace() будет быстрее по сравнению с циклом while(), учитывая большую строку или массив? Если нет, то каковы его преимущества вне Code Golf?
  • Есть ли способ оптимизировать этот процесс, делая его как более эффективным, так и беспроблемным, как функциональный второй подход?

Я хотел бы получить представление о том, что происходит за этими процессами.

...

[ Fo (u) r запись: Я рад, что меня вызвали на использование слов "лямбда" и/или "функциональность". Я все еще изучаю понятия, поэтому не предполагайте, что я точно знаю, о чем говорю, и не стесняйтесь исправить меня, если я неправильно использую условия здесь.]

Ответ 1

Почему этот процесс выглядит медленнее в Chrome и (например) быстрее в Firefox?

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

Есть ли вероятность того, что метод replace() будет быстрее по сравнению с циклом while() с большим количеством строк или массива?

Да, он должен делать меньше конкатенации строк и назначений, а, как вы сказали, меньше инициализации переменных. Однако вы можете проверить это только для подтверждения моих предположений (а также взглянуть на http://jsperf.com/match-and-substitute/4 для других фрагментов - вы, например, можете увидеть Оптимизацию Opera lambda-replace2, который не использует arguments).

Если нет, каковы его преимущества вне Code Golf?

Я не думаю, что гольф для кодов - это правильный термин. Качество программного обеспечения связано с читабельностью и понятностью, в терминах которых краткость и элегантность (что является субъективным) функционального кода являются причинами использования этого подхода (на самом деле я никогда не видел замену на exec, substring и повторно конкатенации).

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

Вам не нужна переменная remainder. rgx имеет свойство lastIndex, которое автоматически продвигает матч через str.

Ответ 2

Ваш цикл while с exec() немного медленнее, чем должен быть, поскольку вы выполняете дополнительную работу (substring), когда используете exec() в неглобальном регулярном выражении. Если вам нужно пройти все совпадения, вы должны использовать цикл while для глобального регулярного выражения (флаг g включен); таким образом, вы избегаете выполнения дополнительной работы по обрезке обрабатываемой части строки.

var rgR = /\((\d+)\)/g;
var mch,
    result = '',
    lastAppend = 0;

while ((mch = rgR.exec(str)) !== null) {
    result += str.substring(lastAppend, mch.index) + sub[mch[1]];
    lastAppend = rgR.lastIndex;
}
result += str.substring(lastAppend);

Этот фактор не мешает различию производительности между разными браузерами.

Кажется, что разница в производительности связана с реализацией браузера. Из-за незнания с реализацией я не могу ответить, откуда эта разница.

В терминах мощности exec() и replace() имеют одинаковую мощность. Сюда относятся случаи, когда вы не используете возвращаемое значение из replace(). Пример 1. Пример 2.

replace() метод более читабельный (намерение более ясное), чем цикл while с exec() , если используется значение, возвращаемое функцией (т.е. вы делаете реальную замену в анонимной функции). Вам также не нужно самостоятельно восстанавливать замененную строку. Здесь replace предпочтительнее exec(). (Надеюсь, это ответ на вторую часть вопроса 2).

Я бы предположил, что exec() используется для целей, отличных от замены (за исключением очень особых случаев, таких как this). Замена, если возможно, должна выполняться с помощью replace().

Оптимизация необходима, только если производительность сильно ухудшается при фактическом вводе. У меня нет никакой оптимизации для показа, так как 2 только возможных варианта уже проанализированы, что противоречит производительности между двумя разными браузерами. Это может измениться в будущем, но на данный момент вы можете выбрать тот, который имеет лучший худший результат для браузера для работы.