【问题标题】:Why is String.replace() with lambda slower than a while-loop repeatedly calling RegExp.exec()?为什么带有 lambda 的 String.replace() 比 while 循环重复调用 RegExp.exec() 慢?
【发布时间】:2013-03-06 02:56:10
【问题描述】:

一个问题:

我想处理一个字符串 (str),以便将任何带括号的数字(与 rgx 匹配)替换为从数组中适当位置获取的值 (sub):

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

鉴于上面声明的变量,result 应该是“这是一个测试语句”。

两种解决方案:

This works:

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;

但我认为下面的代码会更快。它的变量更少,更简洁,并使用匿名函数表达式(或 lambda):

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

This works too,但是速度我错了; in Chrome it's surprisingly (~50%, last time I checked) slower!

...

三个问题:

  1. 为什么这个过程在 Chrome 中看起来更慢,而在 Firefox 中(例如)更快?
  2. 在给定更大的字符串或数组的情况下,replace() 方法是否有可能比while() 循环更快?如果不是,它在Code Golf之外有什么好处?
  3. 有没有办法优化这个过程,让它像第二种功能方法一样更高效、更轻松?

我欢迎任何关于这些过程背后发生了什么的见解。

...

[Fo(u)r 记录:我很高兴有人指出我对“lambda”和/或“功能性”这两个词的使用。我还在学习这些概念,所以不要以为我完全知道我在说什么,如果我在这里误用了这些术语,请随时纠正我。]

【问题讨论】:

  • 在 JS 中不叫“lamba”,而是“(匿名)函数表达式”,虽然是同一个概念。 “功能性”是正确的术语。
  • JSYK,在 IE10 中匿名函数方法的 ops/s 大约是两倍
  • @Bergi 感谢您的澄清和保证。我知道术语“匿名函数表达式”(以及 JavaScript 用户更常见的用法)。但我认为“lambda”是足够的同义词 [?],可能会吸引那些对函数式编程有更一般知识的人,因此会吸引那些可能能够更多地谈论 w/r 优化的人的答案/t 不同的编程范式。我现在更新了问题以包含术语“匿名函数表达式”。希望这更有意义。
  • @Kolink 感谢您指出这一点。同样有趣的是,IE 10.0 的整体性能优于 Firefox 19.0...
  • @guypursey:是的,“lambda”也很好,也很明确,它只是针对更多了解函数式编程的人:-)

标签: javascript regex performance optimization lambda


【解决方案1】:

为什么这个过程在 Chrome 中看起来更慢,而在 Firefox 中(例如)更快?

因为它必须调用一个(非本地)函数,这很昂贵。 Firefox 的引擎可以通过识别和inlining the lookup 来优化它。

如果给定更大的字符串或数组,replace() 方法是否有可能比 while() 循环更快?

是的,它必须做更少的字符串连接和赋值,并且——正如你所说——需要初始化的变量更少。然而,您只能对其进行测试以证明我的假设(还可以查看 http://jsperf.com/match-and-substitute/4 以了解其他 sn-ps - 例如,您可以看到 Opera 优化了不使用 arguments 的 lambda-replace2)。

如果不是,它在 Code Golf 之外有什么好处?

我不认为代码高尔夫是正确的术语。软件质量是关于可读性和可理解性的,用功能代码的简洁性优雅(虽然这是主观的)是使用这种方法的原因(实际上我'我们从未见过用execsubstring 替换并重新连接)。

有没有办法优化这个过程,让它像第二种功能方法一样更高效、更轻松?

您不需要 remainder 变量。 rgx 有一个lastIndex property,它将自动通过str 推进匹配。

【讨论】:

  • actually I've never seen a replace with exec, substring and re-concatenation 现在是you see it。我找不到使用 replace 的方法(我们不必丢弃返回值 - 您始终可以运行自己的代码并丢弃返回值)。
  • @nhahtdh:好的,但这只是在 JS 中进行lookbehind 的一种解决方法。你可以使用类似.replace(/\[(\w+)\]/g, function(m, key, offset, str) { if (offset>0 && str.charAt(offset-1) == '[') return m.slice(1); return table[key]; })
  • @Bergi 而 Chrome 在优化手工代码方面显然要好得多;)
  • @Bergi:你能重新运行测试吗?添加新测试时,我有点覆盖了修订版。
  • 谢谢,我已经 +1 了,但在这种情况下,非本地函数是什么意思?我认为replace() 是 JavaScript 原生的;我还没有从图书馆里拿出来。另外,“识别和内联查找”是什么意思?我知道您只是在说可能 Firefox 正在做什么,但我不确定这意味着什么。对不起,如果这是一个基本问题;我将自己描述为精通 JavaScript,但我不太了解任何浏览器中的解释/执行是如何发生的,因此可能只需要指出有关该主题的良好介绍或入门。谢谢。
【解决方案2】:

带有exec()while 循环比应有的速度稍慢,因为在非全局正则表达式上使用exec() 时,您正在做额外的工作(substring)。如果您需要遍历所有匹配项,则应在全局正则表达式上使用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() 的返回值的情况。 Example 1Example 2.

replace() 方法比带有exec()while 循环更具可读性(意图更清晰) if 您正在使用函数返回的值(即您正在执行真正的替换在匿名函数中)。您也不必自己重建替换的字符串。这是replace 优于exec() 的地方。 (我希望这能回答问题 2 的第二部分)。

我认为exec() 将用于替换以外的其他目的(除了非常特殊的情况,例如this)。如果可能,应使用replace() 进行替换。

只有在实际输入的性能严重下降时才需要进行优化。我没有任何优化可以展示,因为已经分析了 2 个唯一可能的选项,2 个不同浏览器之间的性能相互矛盾。这在未来可能会改变,但现在,您可以选择具有更好跨浏览器性能的最差浏览器来使用。

【讨论】:

  • 感谢有关 while 循环中全局标记正则表达式的提示。出于某种原因,我忽略了这种可能性,所以至少我值得 +1 :-) 我也同意你的观点,即选择跨浏览器性能更好的解决方案;这是一个很好的实际措施。
猜你喜欢
  • 2017-12-17
  • 2014-02-17
  • 2018-05-24
  • 1970-01-01
  • 2014-01-21
  • 2012-10-01
  • 2012-11-25
  • 1970-01-01
  • 2020-12-31
相关资源
最近更新 更多