这种 asm 风格的 "x | 0" 是一些 JavaScript 程序员现在使用的吗?

13

我见过一些性能关键的JavaScript代码,比如这个项目上的代码,它广泛使用了按位或运算符与0。例如:

GameBoyAdvanceCPU.prototype.write8 = function (address, data) {
address = address | 0;
data = data | 0;
this.memory.memoryWrite8(address | 0, data | 0);

我知道使用 "|0" 将数字转换为整数,但这里不是这种情况,因为这些始终是整数。它看起来有点像 asm.js,这是为了告诉 js 引擎我们正在使用整数,从而允许进行一些优化吗?如果是这样,哪些浏览器将进行这些优化?

任何关于如何工作的指导都将不胜感激。


2
这是 asm.js 风格。它将值强制转换为32位整数,比浮点数更快。 - Raymond Chen
1
代码库中有一个被注释掉的"use asm"实例,这表明这确实应该是asm.js,或者至少曾经是。 - user2357112
1
寻找另一个提示 - “我将继续重写一些使用匿名函数数组作为解决方案的io寄存器代码为开关。” :) -(https://github.com/chrisdickinson/IodineGBA/commit/44b84f59ae8f95557b2afba4154396c193d54870) - l'L'l
2
从问题跟踪器来看,重点是优化:“老实说,当运行此代码时,Firefox和Chrome并不像它们应该的那样快...我在代码中放置了类型强制保护,因此浏览器应该更经常地捕捉到它们。”- https://github.com/taisel/IodineGBA/issues/21 - fgb
1
它可以分解出类型,即使它不是asm.js。如果你按照逻辑走,传递到函数中的变量,然后在内部调用函数中传递的变量都会被强制转换为int。这个逻辑应该适用于现代浏览器,即使它们不支持asm.js,但越来越聪明地遵循类型。 - Grant Galitz
显示剩余5条评论
4个回答

6
根据 JavaScript Performance for Madmen
将整数算术表达式包装在( ) | 0中,可以让运行时确信您正在执行整数算术而不是浮点算术。这使它能够避免检查溢出并在许多情况下生成更快的代码。
该页面指出对于“大多数”JavaScript运行时是正确的,但没有说明哪些是。
作为第二个来源,Writing Fast JavaScript For Games & Interactive Applications指出:
要告诉JavaScript引擎我们想要存储整数值[...],我们可以使用按位或运算符:
第三个来源来自Microsoft's Writing efficient JavaScript page:
[...] 明确告诉JavaScript运行时使用整数算术[...]使用按位或运算符
此外,除了在注释中,上述页面中没有提到 asm.js,因此我怀疑这样的优化适用于未明确标记为 asm/in 的代码/在不明确识别它的浏览器中。

1
我已经多次听到这种说法,但除了快速转换之外,我还没有看到任何人真正展示出直接的速度提升。 - Etheryte

3
参考Ecmascript 5规范:11.10 二进制位运算符中的内容,即

产生式A : A @ B,其中@是上述产生式中的一个按位运算符(&; ^; |),其计算如下:

lref为评估A的结果。
lvalGetValue(lref)
rref为评估B的结果。
rvalGetValue(rref)
lnumToInt32(lval)
rnumToInt32(rval)
返回将按位运算符@应用于lnumrnum的结果。结果是有符号32位整数。

而注意到ToInt32()的定义如下:

对输入参数调用ToNumber,得到number
如果numberNaN+0−0+∞−∞,则返回+0
posIntsign(number) * floor(abs(number))
int32bitposInt2^32的结果;即一个有限的Number类型的整数值k,符号为正,绝对值小于2^32,使得posIntk之间的差在数学上是2^32的整数倍。
如果int32bit大于等于2^31,则返回int32bit − 2^32,否则返回int32bit

因此可以逻辑地得出结论(您可以在自己的控制台中确认),例如:
((Math.pow(2, 32)) + 2) | 0 === 2
(Math.pow(2, 31)) | 0 === -2147483648 === -(Math.pow(2, 31))

简而言之,该操作将数字转换为32位整数(这有其技巧,请参见上面的第二个示例和ToInt32()定义以获得解释),然后与零进行逻辑或运算,这不会改变第一次转换的输出。

本质上,这是一种非常经济高效的将数字转换为32位整数的方式,因为它依赖于浏览器内置的ToInt32()函数;并且ToInt32(0)会短路到0(请参见上面的规范说明),因此几乎不会增加额外开销。


1
+1,非常好。不过我仍然对优化方面很感兴趣:为什么要让浏览器调用toInt32,是因为js引擎可以更快地执行某些操作吗? - Diogo Franco
1
@DiogoFranco 正如我在另一个答案中所评论的,我已经看到人们多次声称它提供了性能增益,但我还没有看到任何人演示过实际上确实会导致更好性能的代码,除了将其用作转换以确保参数是给定类型之外。像我刚刚创建的这个 Jsperf测试并没有显示出任何可识别的速度差异(我尝试了许多不同的算术和逻辑操作,使用不同的参数,可以自行进行实验)。 - Etheryte
简单的 jsperf 测试不会显示这种类型的结果的好处。它涉及向浏览器提供静态类型提示,并在浏览器 JIT 运行长期时减少退出类型保护。 - Grant Galitz
@GrantGalitz 我仍在等待有人展示一个实际的可衡量性能提升的演示。 - Etheryte
嗯,我重写了IodineGBA的几个部分,最初它缺少了这些多余的“|0”。我测试了几次添加和删除它们,当我个人对Firefox进行微调时。希望能通过测试更好地概括性能差异,但我仍然认为大型项目与玩具工作台存在不同的编译权衡。 - Grant Galitz
这个 toInt32 函数有没有暴露在某个地方?因为我觉得这样做会更好一些,不是吗? - user12582392

1

你可以在这个fiddle中看到实际效果。

在这种情况下,它会探测变量是否为整数类型,如果不是整数,则将其“flooring”或设置为0。

因此,与a = a || 0有很大的区别,后者会保留值3.2


请尝试使用“3141592658”。 - Bergi
对于那些想知道的人... 3141592658 | 0 的结果是 -1153374638 - Matthias
是的,因为int32的最大值是2,147,483,647。 - user12582392

-2

| 运算符是按位或运算符。它通常用于对两个整数进行逐位OR操作。

在这里使用|运算符类似于使用逻辑或||运算符来提供默认值的快捷方式,但与之不同的是结果仅限于整数(而非字符串等等)。

address = address | 0;

意思是“如果地址是一个数字,就使用它;否则,将其设置为0”。


这么做的目的是什么?为什么要优先使用它而不是 ||(如果是真值,则会实际短路)? - zerkms
@zerkms,我不确定。我只能猜测他们可能想确保它是一个数字,以便它不会损坏代码。如果地址是“foo”,|| 就不会生效... - BernieDADA
如果一个操作在 | 0 的左侧,结果必须是整数。这是对引擎的提示,它不需要在此处使用浮点数。如果代码的其余部分(通常在函数内)也在每个需要将 Number 语义转换回浮点数的地方都这样做,优化的引擎会注意到它们可以专门使用32位整数(而不改变语义),这样更快。 - user5311618

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接