【问题标题】:Why are two calls to string.charCodeAt() faster than having one with another one in a never reached if?为什么对 string.charCodeAt() 的两次调用比在从未达到的情况下一次调用一次更快?
【发布时间】:2019-01-13 07:23:45
【问题描述】:

我在 nodejs/chrome/v8 中发现了一个奇怪的行为。看来这段代码:

var x = str.charCodeAt(5);
x = str.charCodeAt(5);

比这更快

var x = str.charCodeAt(5); // x is not greater than 170
if (x > 170) {
  x = str.charCodeAt(5);
}

起初我虽然可能比较比实际的第二次调用更昂贵,但是当 if 块内的内容不调用 str.charCodeAt(5) 时,性能与单次调用相同。

这是为什么?我最好的猜测是 v8 正在优化/去优化某些东西,但我不知道如何准确地解决这个问题或如何防止这种情况发生。

这里是 jsperf 的链接,它至少在我的机器上很好地展示了这种行为: https://jsperf.com/charcodeat-single-vs-ifstatment/1


背景:我发现这一点的原因是我试图优化babel-parser 内部的令牌读取。

我测试过,str.charCodeAt() 的速度是str.codePointAt() 的两倍,所以我可以替换这段代码:

var x = str.codePointAt(index);

var x = str.charCodeAt(index);
if (x >= 0xaa) {
  x = str.codePointAt(index);
}

但是由于上述行为,第二个代码没有给我任何性能优势。

【问题讨论】:

  • 在 Firefox 中,您的第二个选项比第一个选项更快(对于拉丁字符)。看起来 V8 中有些问题。
  • var 还是const?是charCodeAt 还是codePointAt?您问题中的 sn-ps 和您的 jsperf 屏幕截图中的 sn-ps 做的事情完全不同。
  • 是的,var y = str;(其中 y 之后永远不会使用)可以被轻易地识别为死代码,在空 if 的情况下的无副作用比较也是如此声明。
  • 如果您正在优化 babel-parser,则将其用作适当的基准,而不是 trying to do micro-benchmarks
  • 您的第一个代码块在任何合规环境中都是 TypeError。除了在(必需的)初始化程序中之外,您不能分配给 const

标签: javascript performance v8


【解决方案1】:

V8 开发人员在这里。正如 Bergi 指出的那样:不要使用微基准来告知此类决策,因为它们会误导您。

看到每秒数亿次操作的结果通常意味着优化编译器能够消除您的所有代码,并且您正在测量空循环。您必须查看生成的机器代码,看看是否发生了这种情况。

当我将四个 sn-ps 复制到一个小的独立文件中以进行本地调查时,我看到了截然不同的性能结果。两者中哪一个更接近您的实际用例?不知道。这样一来,对这里发生的事情的任何进一步分析就变得毫无意义。

作为一般经验法则,分支比直线代码慢(在所有 CPU 和所有编程语言上)。所以(除了死代码消除和其他微基准测试陷阱之外)如果“两次”案例实际上比两个“如果”案例中的任何一个都快,我不会感到惊讶。也就是说,调用String.charCodeAt 很可能足以抵消这种影响。

【讨论】:

  • 感谢您的回答和详细信息。我并不是盲目地尝试进行微优化,而是尝试查看标记器是否可以改进,因为它是最慢的部分之一。它使用 .codePointAt 扫描每个字符,而大多数文件不会包含太多 unicode。我会继续调查。
  • 优化 很好,适用于性能关键代码;微基准测试往往会产生误导(除非您已验证您实际上正在衡量您认为自己正在衡量的内容)。我建议使用一些真实案例作为基准(即获取一堆真实代码并通过你的真实标记器运行它),并使用 that 框架来评估标记器内部的不同实现策略.如果您在这些情况下看不出有什么不同,那就没有什么不同。
猜你喜欢
  • 2011-04-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多