2

関数が呼び出されるたびにマイクロ最適化が作成されないように、内部関数をこの関数の外に移動しますか?

この特定のケースでは、doMoreStuff関数は 内でのみ使用されdoStuffます。このようなローカル関数を持つことについて心配する必要がありますか?

function doStuff() {
    var doMoreStuff = function(val) {
         // do some stuff
    }

    // do something
    for (var i = 0; i < list.length; i++) {
         doMoreStuff(list[i]);
         for (var  j = 0; j < list[i].children.length; j++) {
              doMoreStuff(list[i].children[j]);
         }
    }
    // do some other stuff

}

実際の例は次のようになります。

function sendDataToServer(data) {
    var callback = function(incoming) {
         // handle incoming
    }

    ajaxCall("url", data, callback);

} 
4

6 に答える 6

4

これが「マイクロ最適化」のカテゴリに該当するかどうかはわかりません。私はノーと言うでしょう。

ただし、電話をかける頻度によって異なりますdoStuff。頻繁に呼び出す場合、関数を何度も作成する必要はなく、間違いなくオーバーヘッドが追加されます。

「ヘルパー関数」をグローバル スコープに含めたくないが再作成は避けたい場合は、次のようにラップできます。

var doStuff = (function() {
    var doMoreStuff = function(val) {
         // do some stuff
    }
    return function() {
        // do something
        for (var i = 0; i < list.length; i++) {
            doMoreStuff(list[i]);
        }
        // do some other stuff 
    }
}());

返される関数はクロージャなので、 にアクセスできますdoMoreStuff。外側の関数はすぐに実行されることに注意してください ( (function(){...}()))。

または、関数への参照を保持するオブジェクトを作成します。

var stuff = {
    doMoreStuff: function() {...},
    doStuff: function() {...}
};

カプセル化、オブジェクト作成パターン、その他の概念の詳細については、本JavaScript パターンを参照してください。

于 2011-01-20T13:21:44.250 に答える
1

元の質問は 2011 年に出されました。それ以降の Node.js の台頭を考えると、この問題を再検討する価値があると思いました。サーバー環境では、あちこちで数ミリ秒が重要になる場合があります。負荷がかかった状態で応答性を維持するかどうかの違いかもしれません。

内部関数は概念的には優れていますが、JavaScript エンジンのコード オプティマイザーに問題を引き起こす可能性があります。次の例は、これを示しています。

function a1(n) {
    return n + 2;
}

function a2(n) {
    return 2 - n;
}

function a() {
    var k = 5;
    for (var i = 0; i < 100000000; i++) {
        k = a1(k) + a2(k);
    }
    return k;
}

function b() {
    function b1(n) {
        return n + 2;
    }

    function b2(n) {
        return 2 - n;
    }

    var k = 5;
    for (var i = 0; i < 100000000; i++) {
        k = b1(k) + b2(k);
    }
    return k;
}

function measure(label, fn) {
    var s = new Date();
    var r = fn();
    var e = new Date();
    console.log(label, e - s);
}

for (var i = 0; i < 4; i++) {
    measure('A', a);
    measure('B', b);
}

コードを実行するためのコマンド:

node --trace_deopt test.js

出力:

[deoptimize global object @ 0x2431b35106e9]
A 128
B 130
A 132
[deoptimizing (DEOPT eager): begin 0x3ee3d709a821 b (opt #5) @4, FP to SP delta: 72]
  translating b => node=36, height=32
    0x7fffb88a9960: [top + 64] <- 0x2431b3504121 ; rdi 0x2431b3504121 <undefined>
    0x7fffb88a9958: [top + 56] <- 0x17210dea8376 ; caller's pc
    0x7fffb88a9950: [top + 48] <- 0x7fffb88a9998 ; caller's fp
    0x7fffb88a9948: [top + 40] <- 0x3ee3d709a709; context
    0x7fffb88a9940: [top + 32] <- 0x3ee3d709a821; function
    0x7fffb88a9938: [top + 24] <- 0x3ee3d70efa71 ; rcx 0x3ee3d70efa71 <JS Function b1 (SharedFunctionInfo 0x361602434ae1)>
    0x7fffb88a9930: [top + 16] <- 0x3ee3d70efab9 ; rdx 0x3ee3d70efab9 <JS Function b2 (SharedFunctionInfo 0x361602434b71)>
    0x7fffb88a9928: [top + 8] <- 5 ; rbx (smi)
    0x7fffb88a9920: [top + 0] <- 0 ; rax (smi)
[deoptimizing (eager): end 0x3ee3d709a821 b @4 => node=36, pc=0x17210dec9129, state=NO_REGISTERS, alignment=no padding, took 0.203 ms]
[removing optimized code for: b]
B 1000
A 125
B 1032
A 132
B 1033

ご覧のとおり、関数 A と B は最初は同じ速度で実行されました。その後、何らかの理由で最適化解除イベントが発生しました。それ以降、B はほぼ 1 桁遅くなります。

パフォーマンスが重要なコードを書いている場合は、内部関数を避けるのが最善です。

于 2015-10-26T17:01:35.340 に答える
0

For optimal speed with a nested function (function within internal scope of an outer function), I suspect you should use declarations, not expressions.

The question asks about "local functions" and optimization, but doesn't specify how the local functions are created. But it should, because the question's answer probably is different for the different techniques by which the "inner function" can be created.

Looking at the answer and test results by @cleong, I suspect that only his answer is using the optimal technique for function creation. There are three ways to create a function, and @cleong is showing us the one that provides fast execution. The three techniques are:

  • constructor
  • declaration
  • expression

Constructor isn't used much, it requires a string that has the text of the function body. This would be useful in reflective programming, where you do a "toString()" to get the function body, modify, then construct a new function. And that, of course, is more-or-less never done.

Declaration is used, but mostly for outer functions, not inner functions (by "inner function" I mean a function nested within another). Yet, based upon @cleong tests, it seems to be very fast; just as fast as an outer function.

Expressions are what everyone uses. This might not be the best idea; but it's what everyone does.

One major difference between function declarations and function expressions is that the declarations are subject to hoisting. Everyone knows that "var" declarations are hoisted; but so are "function" declarations. For things that are hoisted, computations are performed at compile time to determine the memory space that will be needed for the thing. Presumably, one would expect that the inner function is compiled at compile time, and can run much as would a compiled outer function.

I have a copy of Flannigan's "The Definitive Guide" book from about six years ago, and I remember reading the reverse of what I just wrote here. He said something like: expressions are compiled, and declarations are not. While he is the world's "definitive guide" to JavaScript, I have always suspected he might have gotten this one mixed up and backwards. I suspect that function inner declarations are more "ready to go" than are function expressions. The test results on this stackOverflow page seem to confirm my long held suspicions.

Looking at the @cleong test results, it just seems that declaration, not expression, is the way to go for inner functions, if optimal execution speed is a concern.

于 2021-09-07T23:15:37.310 に答える
0

関数が呼び出される頻度に完全に依存します。毎秒 10 回呼び出される OnUpdate 関数の場合、適切な最適化です。ページごとに 3 回呼び出された場合、それはマイクロ最適化です。

便利ですが、ネストされた関数定義は必要ありません (関数の追加の引数に置き換えることができます)。

ネストされた関数の例:

function somefunc() {
    var localvar = 5

    var otherfunc = function() {
         alert(localvar);
    }

    otherfunc();
}

同じことですが、代わりに引数を使用します。

function otherfunc(localvar) {
    alert(localvar);
}

function somefunc() {
    var localvar = 5

    otherfunc(localvar);
}
于 2011-01-20T13:20:46.927 に答える
0

これは完全にマイクロ最適化です。そもそも関数を使用する理由は、コードをよりクリーンにし、保守しやすく、読みやすくするためです。関数は、コードのセクションにセマンティック境界を追加します。各関数は 1 つのことだけを行う必要があり、それをきれいに行う必要があります。したがって、関数が同時に複数のことを実行していることがわかった場合は、それを複数のルーチンにリファクタリングする候補があります。

動作が遅すぎる場合にのみ最適化してください (まだ動作していない場合は、最適化するには時期尚早です。期間)。覚えておいてください、誰も彼らのニーズ/要件よりも速いプログラムに追加料金を支払ったことはありません...

編集:プログラムがまだ完成していないことを考えると、それは時期尚早の最適化でもあります。なぜそれが悪いのですか?最初は、長期的には重要ではないかもしれない何かに時間を費やしています。第二に、最適化によって現実的な意味で何かが改善されたかどうかを確認するためのベースラインがありません。第 3 に、実行する前に保守性と可読性が低下するため、クリーンで簡潔なコードを使用した場合よりも実行するのが難しくなります。第 4 に、プログラムを終了してすべてのニーズを理解するまで、プログラムの他の場所が必要かどうかはわかりませんdoMoreStuff(正確な詳細によっては大穴かもしれませんが、可能性の範囲外ではありません)。

Donnald Knuth が時期尚早の最適化は諸悪の根源であると言ったのには理由があります...

于 2011-01-20T13:23:37.630 に答える
0

平均的な PC での簡単な「ベンチマーク」実行 (説明されていない変数がたくさんあることはわかっているので、明らかなことについてはコメントしないでください。ただし、いずれにせよ興味深いものです):

count = 0;
t1 = +new Date();
while(count < 1000000) {
  p = function(){};
  ++count;
}
t2 = +new Date();
console.log(t2-t1); // milliseconds

たとえば、インクリメントを条件に移動することで最適化できます(実行時間を約100ミリ秒短縮しますが、関数作成の有無の違いには影響しないため、実際には関係ありません)

3回実行すると、次のことが得られました。

913
878
890

次に、関数作成行をコメントアウトすると、3 回の実行で次の結果が得られました。

462
458
464

したがって、純粋に 1000,000 個の空の関数を作成すると、約 0.5 秒追加されます。元のコードがハンドヘルド デバイスで 1 秒間に 10 回実行されていると仮定しても (デバイスの全体的なパフォーマンスがこのラップトップの 1/100 であるとしましょう。これは誇張されています。おそらく 1/10 に近いですが、適切な上限が提供されます)。 、これはこのコンピューターで 1 秒あたり 1000 関数の作成に相当し、1/2000 秒で発生します。そのため、ハンドヘルド デバイスは 1 秒ごとに 1/2000 秒の処理のオーバーヘッドを追加しています... 1 秒ごとに 0.5 ミリ秒はそれほど大きくありません。

この基本的なテストから、PC ではこれは間違いなくマイクロ最適化であり、より弱いデバイス向けに開発している場合は、ほぼ確実に同様であると結論付けます。

于 2011-01-20T13:44:59.067 に答える