【问题标题】:Large substrings ~9000x faster in Firefox than Chrome: why?Firefox 中的大子字符串比 Chrome 快约 9000 倍:为什么?
【发布时间】:2011-09-05 20:56:24
【问题描述】:

基准:http://jsperf.com/substringing

所以,我正在启动我的第一个基于 HTML5 浏览器的客户端项目。它必须将非常非常大的文本文件解析为一个或多个对象数组。我知道我将如何进行编码;我现在主要关心的是尽可能快地获取解析器代码,我的主要测试平台是 Chrome。然而,在查看子字符串方法之间的差异时(我已经很久很久没有接触过 JavaScript),我注意到与 FireFox 相比,Chrome 中的这个基准测试非常慢。为什么?

我的第一个假设是它与 FireFox 的 JS 引擎处理字符串对象的方式有关,并且对于 FireFox,此操作是简单的指针操作,而对于 Chrome,它实际上是在进行硬拷贝。但是,我不确定为什么 Chrome 不会 进行指针操作或 FireFox 。有人有什么见解吗?

JSPerf 似乎正在丢弃我的 FireFox 结果,而不是在 BrowserScope 上显示它们。对我来说,我在 FF4 中的 .substr() 上获得了 9,568,203 ±1.44% Ops/sec。

编辑:所以我在 Chrome 下方看到了 FF3.5 的性能结果。所以我决定测试我的指针假设。这将我带到了我的 Substrings 测试的2nd revision,它在 FF4 中执行 1,092,718±1.62% Ops/sec 与在 Chrome 中执行 1,195±3.81% Ops/sec,速度降低到只有 1000 倍,但在性能上仍然存在莫名其妙的差异。

后记: 不,我一点也不担心 Internet Explorer。我担心尝试提高我的技能并更深入地了解这种语言。

【问题讨论】:

  • 链接位于最顶部的标题中。还是……?
  • 我不知道它是如何实现的,但是如果你正在解析,你可以用迭代字符串替换子字符串吗? AFAIK 许多解析算法更面向流。
  • 我正在使用索引遍历字符串,但我仍然需要提取子字符串才能将数据从文件中取出。
  • 我在 Chrome 11 中看到 ~1.1k/s,在 FF3.6 中看到 ~250/s。
  • 您可以尝试将其解析为流,而不使用任何子字符串类型函数...如果字段开头有标记,只需将每个字符附加到变量中直到结尾达到的归档。该变量将包含整个字段,而不必使用子字符串方法。相比之下,将字符附加到变量应该非常快。

标签: javascript performance google-chrome firefox jsperf


【解决方案1】:

在 Spidermonkey(Firefox 中的 JS 引擎)的情况下,substring() 调用只会创建一个新的“依赖字符串”:一个字符串对象,它存储指向它的子字符串的指针以及开始和结束偏移量.这正是为了使substring() 更快,并且是对不可变字符串的明显优化。

至于为什么 V8 不这样做...可能是 V8 试图节省空间:在依赖字符串设置中,如果您保留子字符串但忘记了原始字符串,则原始字符串无法获取GCed 因为子字符串正在使用它的部分字符串数据。

无论如何,我只是查看了 V8 源代码,看起来他们根本不做任何类型的依赖字符串;但是,cmets 没有解释为什么他们没有解释。

[更新,12/2013]:在我给出上述答案几个月后,V8 增加了对依赖字符串的支持,正如 Paul Draper 指出的那样。

【讨论】:

  • 这个答案是不正确的,因为 V8 没有依赖字符串。甚至有一个V8 bug 表示这会导致小字符串占用大量内存。
  • V8 在 2011 年 8 月左右添加了依赖字符串。请参阅 codereview.chromium.org/7477045>。因此,答案在给出时实际上是正确的。如果您在原始问题中的测试用例上尝试当前的 Chrome,它的行为与当时的 Chrome 完全不同。
【解决方案2】:

您是否从基准测试结果中消除了.length 的读数?

我相信 V8 有一些字符串的表示形式:

1. a sequence of ASCII bytes
2. a sequence of UTF-16 code units.
3. a slice of a string (result of substring)
4. a concatenation of two strings.

数字 4 是字符串 += 高效的原因。

我只是在猜测,但如果他们试图将两个字符串指针和一个长度打包到一个小空间中,他们可能无法使用指针缓存大长度,因此最终可能会在连接的链接列表中行走为了计算长度。这当然假设 Array.prototype.join 从数组部分创建形式 (4) 的字符串。

它确实导致了一个可检验的假设,即使没有缓冲区副本也可以解释差异。

编辑:

我查看了 V8 源代码,StringBuilderConcat 是我开始拉取的地方,尤其是runtime.cc

【讨论】:

    猜你喜欢
    • 2023-03-27
    • 2017-02-03
    • 2019-05-07
    • 2015-10-07
    • 1970-01-01
    • 1970-01-01
    • 2011-11-18
    • 2011-06-21
    • 2012-02-09
    相关资源
    最近更新 更多