请访问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 + 5*5;
b = a + 6/2;
b = a + 7+1;
执行速度比
快得多。
b = a + 25;
b = a + 3;
b = a + 8;
为什么?
b = a + 26.01;
b = a + 3.1;
b = a + 8.2;
b = a + 25;
b = a + 3;
b = a + 8;
+ 5*5
必须使用更快的浮点路径,而+ 25
则不是。请参见引用的jsPerf以获取更多详细信息。一旦你把所有东西都变成了浮点数,+ (5.1 * 5.1)
选项比我们预期的+ 26.01
选项慢。首先,你的测试存在些许缺陷。
你应该比较以下几个问题:
b = a + 8 - 2;
和 b = a + 6
b = a + 8 + 2;
和 b = a + 10
b = a + 8 / 2;
和 b = a + 4
b = a + 8 * 2;
和 b = 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;
与b = Number(a) + 9;
b = a +(7 + 2)
)分类为TOK_NUMBER
(至少在第一次解析级别上),这也不太可能,或者我们在某个地方深入递归。RecycleTree()
中。火狐浏览器版本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(JaegerMonkey),它基于Brian Hackett的类型推断工作进行优化,使其在这种类型的算术代码上也很快。
在 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;
结果:267,527,019,±0.10%,比原来慢了7%
以及
b = a + 25;
b = a + 3;
b = a + 8;
结果:288,678,771,±0.06%,最快
所以... 不是真的... 不知道为什么在Firefox上会这样。
(在Windows Server 2008 R2 / 7 x64上使用Chrome 14.0.835.202 x86进行测试)