Прежде всего, ваш тест слегка испорчен.
Вы должны сравнить следующее:
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;
... проблема полностью исчезает (хотя, очевидно, она намного медленнее, так как мы вызываем статический метод), я испытываю желание поверить, что либо постоянное свертывание нарушено (что не представляется вероятным, поскольку появляется складывание в скобках работать нормально), что Spidermonkey не классифицирует числовые литералы (или числовые выражения, т.е. b = a + ( 7 + 2 )
) как TOK_NUMBER
(по крайней мере, на первом уровне синтаксического анализа), что также маловероятно, или что мы спускаемся куда-то рекурсивно слишком глубоко.
Я не работал с базой кодов Spidermonkey, но мое чувство Spidey говорит мне, что мы где-то теряемся, и у меня такое ощущение, что оно в RecycleTree()
.