Firefox JavaScript算术性能古怪
Firefox JavaScript arithmetics performance oddity
请在firefox上运行此测试。
http://jsperf.com/static-arithmetic你怎么解释这个结果?
b = a + 5*5;
b = a + 6/2;
b = a + 7+1;
的执行速度比
快得多b = a + 25;
b = a + 3;
b = a + 8;
为什么?
首先,你的测试有一点瑕疵。
你应该比较以下内容:
-
b = a + 8 - 2;
vsb = a + 6
-
b = a + 8 + 2;
vsb = a + 10
-
b = a + 8 / 2;
vsb = a + 4
-
b = a + 8 * 2;
vsb = a + 16
您会注意到一些有趣的事情:只有在第二对项中包含+
或-
的问题较慢(除法和乘法都可以)。加法/减法和乘法/除法的实现必须有明显的区别。确实有:
让我们来看看加法和乘法(jsparse.cpp):
JSParseNode *
Parser::addExpr()
{
JSParseNode *pn = mulExpr();
while (pn &&
(tokenStream.matchToken(TOK_PLUS) ||
tokenStream.matchToken(TOK_MINUS))) {
TokenKind tt = tokenStream.currentToken().type;
JSOp op = (tt == TOK_PLUS) ? JSOP_ADD : JSOP_SUB;
pn = JSParseNode::newBinaryOrAppend(tt, op, pn, mulExpr(), tc);
}
return pn;
}
JSParseNode *
Parser::mulExpr()
{
JSParseNode *pn = unaryExpr();
while (pn && (tokenStream.matchToken(TOK_STAR) || tokenStream.matchToken(TOK_DIVOP))) {
TokenKind tt = tokenStream.currentToken().type;
JSOp op = tokenStream.currentToken().t_op;
pn = JSParseNode::newBinaryOrAppend(tt, op, pn, unaryExpr(), tc);
}
return pn;
}
但是,正如我们所知,这里没有太大的区别。两者都以类似的方式实现,都调用newBinaryOrAppend()
..那么这个函数到底是什么呢?
(剧透:它的名字可能会泄露为什么加法/减法更昂贵。再看一下jsparse.cpp)
JSParseNode *
JSParseNode::newBinaryOrAppend(TokenKind tt, JSOp op, JSParseNode *left, JSParseNode *right,
JSTreeContext *tc)
{
JSParseNode *pn, *pn1, *pn2;
if (!left || !right)
return NULL;
/*
* Flatten a left-associative (left-heavy) tree of a given operator into
* a list, to reduce js_FoldConstants and js_EmitTree recursion.
*/
if (PN_TYPE(left) == tt &&
PN_OP(left) == op &&
(js_CodeSpec[op].format & JOF_LEFTASSOC)) {
if (left->pn_arity != PN_LIST) {
pn1 = left->pn_left, pn2 = left->pn_right;
left->pn_arity = PN_LIST;
left->pn_parens = false;
left->initList(pn1);
left->append(pn2);
if (tt == TOK_PLUS) {
if (pn1->pn_type == TOK_STRING)
left->pn_xflags |= PNX_STRCAT;
else if (pn1->pn_type != TOK_NUMBER)
left->pn_xflags |= PNX_CANTFOLD;
if (pn2->pn_type == TOK_STRING)
left->pn_xflags |= PNX_STRCAT;
else if (pn2->pn_type != TOK_NUMBER)
left->pn_xflags |= PNX_CANTFOLD;
}
}
left->append(right);
left->pn_pos.end = right->pn_pos.end;
if (tt == TOK_PLUS) {
if (right->pn_type == TOK_STRING)
left->pn_xflags |= PNX_STRCAT;
else if (right->pn_type != TOK_NUMBER)
left->pn_xflags |= PNX_CANTFOLD;
}
return left;
}
/*
* Fold constant addition immediately, to conserve node space and, what's
* more, so js_FoldConstants never sees mixed addition and concatenation
* operations with more than one leading non-string operand in a PN_LIST
* generated for expressions such as 1 + 2 + "pt" (which should evaluate
* to "3pt", not "12pt").
*/
if (tt == TOK_PLUS &&
left->pn_type == TOK_NUMBER &&
right->pn_type == TOK_NUMBER) {
left->pn_dval += right->pn_dval;
left->pn_pos.end = right->pn_pos.end;
RecycleTree(right, tc);
return left;
}
pn = NewOrRecycledNode(tc);
if (!pn)
return NULL;
pn->init(tt, op, PN_BINARY);
pn->pn_pos.begin = left->pn_pos.begin;
pn->pn_pos.end = right->pn_pos.end;
pn->pn_left = left;
pn->pn_right = right;
return (BinaryNode *)pn;
}
给定上述,特别是常数折叠:
if (tt == TOK_PLUS &&
left->pn_type == TOK_NUMBER &&
right->pn_type == TOK_NUMBER) {
left->pn_dval += right->pn_dval;
left->pn_pos.end = right->pn_pos.end;
RecycleTree(right, tc);
return left;
}
并且考虑到在表述像
这样的问题时-
b = Number(a) + 7 + 2;
vsb = Number(a) + 9;
…问题完全消失了(尽管由于我们调用了一个静态方法,它显然要慢得多),我很想相信常量折叠被破坏了(这似乎不太可能,因为括号折叠似乎工作得很好),Spidermonkey没有将数字字面量(或数字表达式,即b = a + ( 7 + 2 )
)分类为TOK_NUMBER
(至少在第一个解析级别),这也不太可能,或者我们在递归的某个地方下降得太深了。
我没有使用过Spidermonkey代码库,但我的蜘蛛侠感觉告诉我我们在某个地方迷路了,我有一种感觉,它在RecycleTree()
。
在Firefox中,它似乎与浮点数学和整数数学有关,其中浮点要快得多。当我添加一些浮点运算时,您可以看到差异:http://jsperf.com/static-arithmetic/14.
这个更快:
b = a + 26.01;
b = a + 3.1;
b = a + 8.2;
比:b = a + 25;
b = a + 3;
b = a + 8;
我所能猜到的是Firefox有一些浮点优化,不适用于整数数学,或者当涉及浮点数时,代码不知怎么地采取了不同的路径。
因此,将此信息推断到您的原始答案,+ 5*5
必须使用更快的浮动路径,因为+ 25
不是。更多详细信息,请参阅参考jsPerf。
一旦你让所有的东西都浮动,+ (5.1 * 5.1)
选项比+ 26.01
选项慢,就像我们期望的那样。
Firefox版本4-8有两个不同的jit: Tracemonkey (tracejit)和JaegerMonkey (methodjit)。TraceMonkey在简单的数字代码上要好得多;JaegerMonkey对各种分支代码的处理要好得多。
有一个启发式用于决定使用哪个JIT。它考虑了许多因素,其中大多数因素在这里是不相关的,但对于这个测试用例来说,重要的是循环体中的算术操作越多,TraceMonkey就越有可能被使用。
您可以通过更改javascript.options.tracejit.content
和javascript.options.methodjit.content
的值来测试这一点,以强制代码在一个或另一个JIT下运行,然后看看它如何影响性能。
看起来常量折叠在使测试用例表现相同方面并没有节省时间,因为Spidermonkey不能将a + 7 + 1 = (a + 7) + 1
常量折叠为a + 8
,因为它不知道a
是什么(例如,"" + 7 + 1 == "71"
而"" + 8 == "8"
)。如果你把它写为a + (7 + 1)
,那么突然你就会得到另一个JIT在这段代码上运行。
所有这些都证明了从微基准测试推断到实际代码的危险。;)
哦,Firefox 9只有一个JIT(基于Brian Hackett的类型推断工作的优化JaegerMonkey,使其在处理这类算术代码时速度也很快)。
在Windows XP上测试Firefox 3.6.23分配算法
b = a + 5*5;
b = a + 6/2;
b = a + 7+1;
67,346,939 ±0.83%11% slower assign plain
b = a + 25;
b = a + 3;
b = a + 8;
75,530,913 ±0.51%fastest
Chrome不支持
我:b = a + 5*5;
b = a + 6/2;
b = a + 7+1;
结果:267527019±0.10%,慢7%
和
b = a + 25;
b = a + 3;
b = a + 8;
结果:288678771±0.06%,最快
所以,不是真的…不知道为什么Firefox会这样。
(在Windows Server 2008 R2/7 x64上测试Chrome 14.0.835.202 x86)
- 在javascript中搜索项目列表的性能
- JavaScript数组优化以提高性能
- JavaScript-===vs===运算符性能
- Javascript对象中的跨浏览器密钥查找性能
- 它是否创建了许多不利于JavaScript性能的变量
- html5画布和纯Javascript/GWT的游戏性能
- javascript getAttribute是否会影响性能或触发布局
- Javascript性能-在dom上迭代并添加侦听器
- 在JavaScript中,在对象上装箱每个数字和字符串的性能成本是多少
- 从自执行函数返回函数的Javascript性能命中率
- javascript散列/数组混合性能
- javascript:将文本附加到文本区域的性能
- 性能惩罚JavaScript回调函数
- 行之间有空格会影响 JavaScript 代码的性能吗?
- JavaScript 库的性能开销
- 按“Levenshtein Distance”对数组进行排序,在Javascript中具有最佳性能
- 将javascript和css内联放在一个缩小的html文件中以提高性能
- 数字精度如何影响 JavaScript 的性能,或者会影响性能
- 数组对象与数组文字-性能JavaScript
- 图表运行在后台导致低性能(JavaScript + jQuery)