为什么两次调用 string.charCodeAt() 比一次调用另一次调用更快?

Why are two calls to string.charCodeAt() faster than having one with another one in a never reached if?

我在 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 是 optimizing/deoptimizing 东西,但我不知道如何准确地解决这个问题或如何防止这种情况发生。

这是 jsperf 的 link,它至少在我的机器上很好地演示了这种行为: 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);
}

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

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

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

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

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