Почему 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 только возможных варианта уже проанализированы, что противоречит производительности между двумя разными браузерами. Это может измениться в будущем, но на данный момент вы можете выбрать тот, который имеет лучший худший результат для браузера для работы.