このテストは 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;
なんで?
このテストは 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;
なんで?
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
まず第一に、あなたのテストにはわずかな欠陥があります。
以下を比較する必要があります。
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
興味深い点に気付くでしょう:項の 2 番目のペアにある問題のみが遅くなります (除算と乗算は問題ありません)。加算/減算と乗算/除算の実装には明確な違いがある必要があります。そして確かにある:+
-
それでは、加算と乗算 ( 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()
ます。
Firefox バージョン 4 ~ 8 には、Tracemonkey (tracejit) と JaegerMonkey (methodjit) の 2 つの異なる JIT があります。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"
while "" + 8 == "8"
)。そのように書くとa + (7 + 1)
、突然、このコードで他の JIT が実行されます。
これらすべてが、マイクロベンチマークから実際のコードに外挿することの危険性を証明しています。;)
ああ、Firefox 9 には JIT が 1 つしかありません (Brian Hackett の型推論作業に基づいて最適化された JaegerMonkey は、この種の算術コードでも高速になります)。
Windows XP 上の Firefox 3.6.23 でのテスト Ops/sec 割り当て演算のテスト
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 /7x64上のChrome14.0.835.202x86でのテスト)