JSpeed-Javascriptの最適化と呼ばれるこのプロジェクトを偶然見つけたとき、私はGoogle Codeを閲覧していました。
最適化の1つがi++
〜++i
forループステートメント。
最適化前
for (i=0;i<1;i++) {}
for (var i = 0, j = 0; i < 1000000; i++, j++) {
if (i == 4) {
var tmp = i / 2;
}
if ((i % 2) == 0) {
var tmp = i / 2;
i++;
}
}
var arr = new Array(1000000);
for (i = 0; i < arr.length; i++) {}
最適化後
for(var i=0;i<1;++i){}
for(var i=0,j=0;i<1000000;++i,++j){if(i==4){var tmp=i>>1;}
if((i&1)==0){var tmp=i>>1;i++;}}
var arr=new Array(1000000);for(var i=0,arr_len=arr.length;i<arr_len;++i){}
私は事前と事後の増分が何をするか知っていますが、これがコードをどのようにスピードアップするかについての考えはありますか?
これは私が読んだものであり、あなたの質問に答えることができます:「前インクリメント(++i
)はi
の値に1を加え、i
を返します。対照的に、 i++
はi
を返し、それに1を加算します。これは理論的にはの結果、インクリメント操作が適用される前にi
の値を格納する一時変数が作成されます。 " 。
これは偽の最適化です。私が理解している限り、あなたは1オペコードを節約しています。この手法でコードを最適化しようとしているのであれば、間違った方法で行ったことになります。また、ほとんどのコンパイラ/インタプリタはこれをとにかく最適化します( 参照1 )。要するに私は心配していません。 しかし、本当に心配なら、i+=1
。
ここに私がやったばかりの汚いベンチマークがあります
var MAX = 1000000, t=0,i=0;
t = (new Date()).getTime();
for ( i=0; i<MAX;i++ ) {}
t = (new Date()).getTime() - t;
console.log(t);
t = (new Date()).getTime();
for ( i=0; i<MAX;++i ) {}
t = (new Date()).getTime() - t;
console.log(t);
t = (new Date()).getTime();
for ( i=0; i<MAX;i+=1 ) {}
t = (new Date()).getTime() - t;
console.log(t);
生の結果
Post Pre +=
1071 1073 1060
1065 1048 1051
1070 1065 1060
1090 1070 1060
1070 1063 1068
1066 1060 1064
1053 1063 1054
最低と最高を削除しました
Post Pre +=
1071 ---- 1060
1065 ---- ----
1070 1065 1060
---- 1070 1060
1070 1063 ----
1066 1060 1064
---- 1063 1054
平均
1068.4 1064.2 1059.6
これは100万回を超え、結果は平均して9ミリ秒以内であることに注意してください。 JavaScriptでのほとんどの反復処理がはるかに小さなセット(たとえば、DOMコンテナー)で行われることを考えると、あまり最適化されていません。
理論的には、ポストインクリメント演算子mayを使用すると、一時変数が生成されます。実際には、JavaScriptコンパイラーは、特にそのような些細なケースでは、それを回避するのに十分スマートです。
たとえば、そのサンプルコードを考えてみましょう:
sh$ cat test.js
function preInc(){
for(i=0; i < 10; ++i)
console.log(i);
}
function postInc(){
for(i=0; i < 10; i++)
console.log(i);
}
// force lazy compilation
preInc();
postInc();
その場合、NodeJSのV8コンパイラーは正確に同じバイトコードを生成します(増分については、特にオペコード39から44を参照してください)。
sh$ node --version
v8.9.4
sh$ node --print-bytecode test.js | sed -nEe '/(pre|post)Inc/,/^\[/p'
[generating bytecode for function: preInc]
Parameter count 1
Frame size 24
77 E> 0x1d4ea44cdad6 @ 0 : 91 StackCheck
87 S> 0x1d4ea44cdad7 @ 1 : 02 LdaZero
88 E> 0x1d4ea44cdad8 @ 2 : 0c 00 03 StaGlobalSloppy [0], [3]
94 S> 0x1d4ea44cdadb @ 5 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdade @ 8 : 1e fa Star r0
0x1d4ea44cdae0 @ 10 : 03 0a LdaSmi [10]
94 E> 0x1d4ea44cdae2 @ 12 : 5b fa 07 TestLessThan r0, [7]
0x1d4ea44cdae5 @ 15 : 86 23 JumpIfFalse [35] (0x1d4ea44cdb08 @ 50)
83 E> 0x1d4ea44cdae7 @ 17 : 91 StackCheck
109 S> 0x1d4ea44cdae8 @ 18 : 0a 01 0d LdaGlobal [1], [13]
0x1d4ea44cdaeb @ 21 : 1e f9 Star r1
117 E> 0x1d4ea44cdaed @ 23 : 20 f9 02 0f LdaNamedProperty r1, [2], [15]
0x1d4ea44cdaf1 @ 27 : 1e fa Star r0
121 E> 0x1d4ea44cdaf3 @ 29 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdaf6 @ 32 : 1e f8 Star r2
117 E> 0x1d4ea44cdaf8 @ 34 : 4c fa f9 f8 0b CallProperty1 r0, r1, r2, [11]
102 S> 0x1d4ea44cdafd @ 39 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44cdb00 @ 42 : 41 0a Inc [10]
102 E> 0x1d4ea44cdb02 @ 44 : 0c 00 08 StaGlobalSloppy [0], [8]
0x1d4ea44cdb05 @ 47 : 77 2a 00 JumpLoop [42], [0] (0x1d4ea44cdadb @ 5)
0x1d4ea44cdb08 @ 50 : 04 LdaUndefined
125 S> 0x1d4ea44cdb09 @ 51 : 95 Return
Constant pool (size = 3)
Handler Table (size = 16)
[generating bytecode for function: get]
[generating bytecode for function: postInc]
Parameter count 1
Frame size 24
144 E> 0x1d4ea44d821e @ 0 : 91 StackCheck
154 S> 0x1d4ea44d821f @ 1 : 02 LdaZero
155 E> 0x1d4ea44d8220 @ 2 : 0c 00 03 StaGlobalSloppy [0], [3]
161 S> 0x1d4ea44d8223 @ 5 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d8226 @ 8 : 1e fa Star r0
0x1d4ea44d8228 @ 10 : 03 0a LdaSmi [10]
161 E> 0x1d4ea44d822a @ 12 : 5b fa 07 TestLessThan r0, [7]
0x1d4ea44d822d @ 15 : 86 23 JumpIfFalse [35] (0x1d4ea44d8250 @ 50)
150 E> 0x1d4ea44d822f @ 17 : 91 StackCheck
176 S> 0x1d4ea44d8230 @ 18 : 0a 01 0d LdaGlobal [1], [13]
0x1d4ea44d8233 @ 21 : 1e f9 Star r1
184 E> 0x1d4ea44d8235 @ 23 : 20 f9 02 0f LdaNamedProperty r1, [2], [15]
0x1d4ea44d8239 @ 27 : 1e fa Star r0
188 E> 0x1d4ea44d823b @ 29 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d823e @ 32 : 1e f8 Star r2
184 E> 0x1d4ea44d8240 @ 34 : 4c fa f9 f8 0b CallProperty1 r0, r1, r2, [11]
168 S> 0x1d4ea44d8245 @ 39 : 0a 00 05 LdaGlobal [0], [5]
0x1d4ea44d8248 @ 42 : 41 0a Inc [10]
168 E> 0x1d4ea44d824a @ 44 : 0c 00 08 StaGlobalSloppy [0], [8]
0x1d4ea44d824d @ 47 : 77 2a 00 JumpLoop [42], [0] (0x1d4ea44d8223 @ 5)
0x1d4ea44d8250 @ 50 : 04 LdaUndefined
192 S> 0x1d4ea44d8251 @ 51 : 95 Return
Constant pool (size = 3)
Handler Table (size = 16)
もちろん、他のJavaScriptコンパイラー/インタープリターmayは他の方法で行いますが、これは疑わしいです。
最後のWordとして、それでも価値があるにもかかわらず、可能な場合は事前インクリメントを使用することをベストプラクティスとして検討します。私は頻繁に言語を切り替えるので、正しいsemanticの構文を使用することを好みますコンパイラのスマートさに頼るのではなく、欲しい。たとえば、最近のCコンパイラでも違いはありません。しかし、C++では、これはoperator++
。
時期尚早の最適化のように聞こえます。アプリがほぼ完成したら、ボトルネックの場所を確認し、必要に応じてボトルネックを最適化します。しかし、ループパフォーマンスの完全なガイドが必要な場合は、これを確認してください。
http://blogs.Oracle.com/greimer/entry/best_way_to_code_a
しかし、JSエンジンの改善とブラウザー間のばらつきのために、これがいつ廃止されるかはわかりません。最善の選択は、問題になるまで心配しないことです。コードを読みやすくします。
編集: this guy によると、事前対事後は統計的に重要ではありません。 (事前に悪化している可能性があります)
最適化は、事前または事後の増分ではありません。これは、除算やmodではなく、ビットごとの 'shift'および 'and'演算子の使用です。
合計サイズを減らすためにJavaScriptを縮小する最適化もあります(ただし、これはランタイムの最適化ではありません)。
Anatoliyのテストでは、前インクリメントテスト関数内に後インクリメントが含まれていました:(
この副作用のない結果は次のとおりです...
function test_post() {
console.time('postIncrement');
var i = 1000000, x = 0;
do x++; while(i--);
console.timeEnd('postIncrement');
}
function test_pre() {
console.time('preIncrement');
var i = 1000000, x = 0;
do ++x; while(--i);
console.timeEnd('preIncrement');
}
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
出力
postIncrement: 3.21ms
preIncrement: 2.4ms
postIncrement: 3.03ms
preIncrement: 2.3ms
postIncrement: 2.53ms
preIncrement: 1.93ms
postIncrement: 2.54ms
preIncrement: 1.9ms
それは大きな違いです。
これはおそらくカーゴカルトプログラミングです。任意の演算子のオーバーロードを持たない言語用にまともなコンパイラ/インタープリタを使用している場合、違いはありません。
この最適化は、C++では理にかなっています。
T x = ...;
++x
その場で値を変更できますが、
T x = ...;
x++
のような裏で何かをすることでコピーを作成する必要があります
T x = ...;
T copy;
(copy = T(x), ++x, copy)
これは、大きな構造体の型や、 `コピーコンストラクターで多くの計算を行う型の場合、コストがかかる可能性があります。
Firebugでテストしたところ、ポストインクリメントとプリインクリメントの間に違いはありませんでした。多分この最適化は他のプラットフォームですか? Firebugテストのコードは次のとおりです。
function test_post() {
console.time('postIncrement');
var i = 1000000, x = 0;
do x++; while(i--);
console.timeEnd('postIncrement');
}
function test_pre() {
console.time('preIncrement');
var i = 1000000, x = 0;
do ++x; while(i--);
console.timeEnd('preIncrement');
}
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
test_post();
test_pre();
出力は次のとおりです。
postIncrement: 140ms
preIncrement: 160ms
postIncrement: 136ms
preIncrement: 157ms
postIncrement: 148ms
preIncrement: 137ms
postIncrement: 136ms
preIncrement: 148ms