オブジェクトがある場合:
var Edge = function() {
this.a = 0;
this.b = 0;
this.prevEdge = null;
this.nextEdge = null;
// +20 other members
}
そして関数:
function ProcessEdge(e)
{
// some processing and then
e = e.nextEdge;
// some processing and then return something
}
この方法で他の関数から呼び出されます:
var e = new Edge();
// some processing and then
var res = ProcessEdge(e);
// e should be now the same object as e.nextEdge
// but it is not, e is still e
関数内で参照が壊れているため、 はe
now である必要がありますがe.nextEdge
、まだです。e
e = e.nextEdge;
ProcessEdge()
ProcessEdge()
最初に考えたのは、 のようなものを使用して関数内のすべてのメンバーを 1 つずつ割り当てることでメンバーの等価性を達成することでしe.a = e.nextEdge.a; e.b = e.nextEdge.b; e.prevEdge = e.nextEdge.prevEdge; e.nextEdge = e.nextEdge.nextEdge;
たが、参照はまだ壊れています。このようにすると、メンバーごとに比較する必要があり、スパゲッティ コードとその遅さがイメージできます。
C# および同様の言語では、変数名の前にキーワードを使用するだけで参照を保持できref
、参照は保持され、速度と単純さと速度は期待どおりです。
さて、機能する方法です(これは私がこれまでに使用した方法です):
function ProcessEdge(e)
{
// some processing and then
e.Value = e.Value.nextEdge; // instead of e = e.nextEdge;
// some processing and then return something
}
// And the calling part:
var e = new Edge();
// some processing and then
var res = (function ()
{
e = { Value: e };
var $res = ProcessEdge(e);
e = e.Value;
return $res;
})
.call(this);
var res = (function() {}...
このメソッドは正常に機能しますが、問題は行が実行されるたびに (AFAIK) 作成されるインライン関数です。これvar res...
はループ内にあり、大量のデータがある場合、1 秒間に 100,000 ~ 500,000 回呼び出されることもあります。
それで、参照保持を達成するためのより速い方法はありますか?
編集:
以下のコメントに従って、速度テストを行いました: http://jsperf.com/closure-vs-compund http://jsperf.com/closure-vs-compund/2
その結果、予想どおり、上記のクロージャ メソッドは非常に遅くなりました。最も速い方法は、関数を完全に削除しProcessEdge()
、代わりにインライン化することであることがわかりました。閉鎖方法の速度は、最速のものの速度の 26.63 % でした。したがって、インライン メソッドはクロージャー メソッドよりも 3.7 倍高速でした。インライン メソッドは次のとおりです。
function main10(a)
{
var res=0, res2, e, b;
for (var i=0;i<10000;i++)
{
e = new Edge();
b = new Edge();
b.a = i*a;
e.nextEdge = b;
// ProcessEdge() as inline version:
e = e.nextEdge;
res += e.a-10;
if (e!=b) res+=1000;
}
return res;
}
しかし、通常は関数を使用する必要があります (また、インタープリターに自動インライン化の機会を与える必要があります)。次に速いのは、「グローバル」参照を使用することでした。速度は最速の 98.45% でした。
はglobal_val
戻り値のホルダーです (Javascript の関数は複数の値を返すことができないため使用されます)。これは、ループの外側にあり、スコープ階層の 1 ステップ上に、global_val
他のすべての関数が作成されると同時に作成される参照オブジェクトがあることを意味します。ProcessEdge()
関数は の値を上書きします。はオブジェクトでglobal_val
あるためe.nextEdge
、参照の軽くて速い上書きのみを意味します (新しいコピーはありません)。e = e.nextEdge
参照の破損を引き起こしますが、参照は現在入ってglobal_val
おり、安全であるため、無関係です。
main11()
関数では、 ProcessEdge() を次のように呼び出し、res += ProcessEdge11(e)
global_val から e への参照を次のように保存しますe = global_val
。
var global_val = {};
function ProcessEdge11(e)
{
e = e.nextEdge;
global_val = e;
return e.a-10;
}
function main11(a)
{
var res=0, res2, e, b;
for (var i=0;i<10000;i++)
{
e = new Edge();
b = new Edge();
b.a = i*a;
e.nextEdge = b;
// some processing and then
res += ProcessEdge11(e);
e = global_val;
if (e!=b) res+=1000;
}
return res;
}
次に、言及するのが賢明な方法がもう 1 つありfunction inside function
ます。上記の 2 つの方法よりも遅い (最速の 84.42%) ですが、かなり単純です。これは基本的に、次を使用するものと同じ事実に依存していますglobal_val
。関数は、参照を壊さない方法でその親スコープ変数 (オブジェクト) にアクセスできます。これは、ローカル変数が作成されず、関数が変数を 1 スコープ上で処理するためです。重要な注意点は、変数が「渡されない」ことと、参照を壊すようなローカル割り当てがないことです。
function main8(a)
{
var pseudo_global_ret = 0;
var res=0, res2, e, b;
for (var i=0;i<10000;i++)
{
e = new Edge();
b = new Edge();
b.a = i*a;
e.nextEdge = b;
_ProcessEdge();
res += pseudo_global_ret;
if (e!=b) res+=1000;
}
return res;
function _ProcessEdge()
{
e = e.nextEdge;
pseudo_global_ret = e.a-10; // instead of return e.a-10
}
}
さらにもう 1 つ注意してください: スワップ関数の Javascript バージョンは、C# の場合とは少し異なりref
ます。C#swap(ref a, ref b)
では を呼び出すことができますが、Javascript ではこれは不可能です。行く方法は次のとおりです。
function swap()
{
var tmp=a;
a=b;
b=tmp;
}
var a={x:0}
var b={x:1}
swap();
ある種の を試すとswap(a,b)
、参照が壊れます。もちろん、グローバル スコープの汚染を防ぐために、これはクロージャ内でラップすることが不可欠です。
誰かが保持を参照するより速い方法を見つけた場合は、この質問に答えてください。