-3

私はmy.class.jsのソースコードを調べて、Firefoxでこれほど高速になる理由を調べてきました。クラスの作成に使用されるコードのスニペットは次のとおりです。

my.Class = function () {
    var len = arguments.length;
    var body = arguments[len - 1];
    var SuperClass = len > 1 ? arguments[0] : null;
    var hasImplementClasses = len > 2;
    var Class, SuperClassEmpty;

    if (body.constructor === Object) {
        Class = function () {};
    } else {
        Class = body.constructor;
        delete body.constructor;
    }

    if (SuperClass) {
        SuperClassEmpty = function() {};
        SuperClassEmpty.prototype = SuperClass.prototype;
        Class.prototype = new SuperClassEmpty();
        Class.prototype.constructor = Class;
        Class.Super = SuperClass;
        extend(Class, SuperClass, false);
    }

    if (hasImplementClasses)
        for (var i = 1; i < len - 1; i++)
            extend(Class.prototype, arguments[i].prototype, false);    

    extendClass(Class, body);

    return Class;
};

このextend関数は、2番目のオブジェクトのプロパティを最初のオブジェクトにコピーするために使用されます(オプションで既存のプロパティをオーバーライドします)。

var extend = function (obj, extension, override) {
    var prop;
    if (override === false) {
        for (prop in extension)
            if (!(prop in obj))
                obj[prop] = extension[prop];
    } else {
        for (prop in extension)
            obj[prop] = extension[prop];
        if (extension.toString !== Object.prototype.toString)
            obj.toString = extension.toString;
    }
};

このextendClass関数は、すべての静的プロパティをクラスにコピーし、すべてのパブリックプロパティをクラスのプロトタイプにコピーします。

var extendClass = my.extendClass = function (Class, extension, override) {
    if (extension.STATIC) {
        extend(Class, extension.STATIC, override);
        delete extension.STATIC;
    }
    extend(Class.prototype, extension, override);
};

これはすべて非常に簡単です。クラスを作成すると、指定したコンストラクター関数が返されます。

ただし、私の理解に勝るのは、このコンストラクターのインスタンスを作成する方が、 Vapor.jsで記述された同じコンストラクターのインスタンスを作成するよりも速く実行される方法です。

これは私が理解しようとしていることです:

  1. my.class.jsのようなライブラリのコンストラクターは、Firefoxで非常に多くのインスタンスをどのようにすばやく作成しますか?ライブラリのコンストラクターはすべて非常に似ています。実行時間も同じではないでしょうか?
  2. クラスの作成方法がインスタンス化の実行速度に影響するのはなぜですか?定義とインスタンス化は別々のプロセスではありませんか?
  3. my.class.jsはどこからこのスピードブーストを得ていますか?コンストラクターコードの実行を高速化する部分が見当たりません。実際、のような長いプロトタイプチェーンをトラバースすると、MyFrenchGuy.Super.prototype.setAddress.call大幅に遅くなるはずです。
  4. コンストラクター関数はJITコンパイルされていますか?もしそうなら、なぜ他のライブラリのコンストラクター関数もJITコンパイルされないのですか?
4

2 に答える 2

12

私は誰かを怒らせるつもりはありませんが、この種のことは本当に注目に値するものではありません. ブラウザー間の速度の違いのほとんどは、JS エンジンによるものです。V8 エンジンは、たとえばメモリ管理に非常に優れています。特に古い IE の JScript エンジンと比較すると。

次の点を考慮してください。

var closure = (function()
{
    var closureVar = 'foo',
    someVar = 'bar',
    returnObject = {publicProp: 'foobar'};
    returnObject.getClosureVar = function()
    {
        return closureVar;
    };
    return returnObject;
}());

前回チェックしたとき、クロムは実際には GC'ed でした。これは、FF と Opera の両方が関数スコープ全体をメモリに保持していたのに対しsomeVar、IIFE の戻り値によって参照されていなかったためです ( によって参照されます)。 このスニペットでは、それは実際には問題ではありませんが、数千行のコードで構成されるモジュール パターン (私の知る限り、ほとんどすべてです) を使用して記述されたライブラリの場合、違いが生じる可能性があります。closure

いずれにせよ、最新の JS エンジンは、単に「ばかげた」解析と実行を行うだけのものではありません。あなたが言ったように、JIT コンパイルが行われていますが、コードを可能な限り最適化するための多くの策略も含まれています。あなたが投稿したスニペットは、FF のエンジンが好きなように書かれている可能性が非常に高いです。
また、どちらが最速のエンジンを搭載しているかについて、Chrome と FF の間で何らかの速度競争が行われていることを覚えておくことも非常に重要です。前回チェックしたとき、Mozilla の Rhino エンジンは Google の V8 よりも優れていると言われていましたが、それが今日でも当てはまるかどうかはわかりません...それ以来、Google と Mozilla の両方がエンジンの開発に取り組んでいます...

結論: さまざまなブラウザ間に速度の違いが存在します。それを否定することはできませんが、1 つの違いは取るに足らないものです。1 つのことだけを何度も実行するスクリプトを作成することは決してありません。重要なのは全体的なパフォーマンスです。
JS もベンチマークするのが難しいバグであることを覚えておく必要があります。コンソールを開いて再帰関数を記述し、FF と Chrome で 100 回実行するだけです。各再帰にかかる時間と全体の実行時間を比較します。次に、数時間待ってからもう一度試してください... FFがトップになる場合もあれば、Chromeの方が速い場合もあります. 私はこの機能でそれを試しました:

var bench = (function()
{
    var mark = {start: [new Date()],
                end: [undefined]},
    i = 0,
    rec = function(n)
    {
        return +(n === 1) || rec(n%2 ? n*3+1 : n/2);
        //^^ Unmaintainable, but fun code ^^\\
    };
    while(i++ < 100)
    {//new date at start, call recursive function, new date at end of recursion
        mark.start[i] = new Date();
        rec(1000);
        mark.end[i] = new Date();
    }
    mark.end[0] = new Date();//after 100 rec calls, first element of start array vs first of end array
    return mark;
}());

ここで、最初の質問に戻ります。

$.extend最初に: あなたが提供したスニペットは、たとえば jQuery のメソッドとはまったく比較になりません:ディープ クローニングは言うまでもなく、実際のクローニングは行われていません。私が調べた他のほとんどのライブラリは循環参照をまったくチェックしません。循環参照をチェックするとプロセス全体が遅くなりますが、ときどき役に立ちます (以下の例 1)。パフォーマンスの違いの一部は、このコードが単に実行することが少ないため、必要な時間が少ないという事実によって説明できます。

第二に、コンストラクターの宣言 (クラスは JS に存在しません) とインスタンスの作成は、実際には 2 つの異なることです (ただし、コンストラクターの宣言自体は、オブジェクトのFunctionインスタンス (正確にはインスタンス) を作成することです)。以下の例 2 に示すように、コンストラクタを記述することで大きな違いが生じる可能性があります. 繰り返しますが、これは一般化であり、特定のエンジンの特定のユースケースには当てはまらない場合があります.インスタンス、たとえその関数がコンストラクターの一部であっても - またはそう言われています。

第三に、あなたが言及したように、長いプロトタイプチェーンをたどることは、あなたが思うほど珍しいことではなく、実際にはそうではありません。例 3 に示すように、2 つまたは 3 つのプロトタイプのチェーンを常にトラバースしています。JS が関数呼び出しを解決したり、式を解決したりする方法に固有のものであるため、これで速度が低下することはありません。

最後に: おそらく JIT コンパイルされていますが、他のライブラリは JIT コンパイルされていないと言うのは通用しません。彼らはそうかもしれませんし、そうでないかもしれません。前に述べたように、異なるエンジンは、あるタスクでは他のタスクよりも優れたパフォーマンスを発揮します... FF JIT コンパイルがこのコードを実行し、他のエンジンは実行しない場合があります。
他のライブラリが JIT コンパイルされない主な理由は、循環参照のチェック、ディープ クローン機能、依存関係 (つまりextend、さまざまな理由でメソッドがあらゆる場所で使用されている) です。

例 1:

var shallowCloneCircular = function(obj)
{//clone object, check for circular references
    function F(){};
    var clone, prop;
    F.prototype = obj;
    clone = new F();
    for (prop in obj)
    {//only copy properties, inherent to instance, rely on prototype-chain for all others
        if (obj.hasOwnProperty(prop))
        {//the ternary deals with circular references
            clone[prop] = obj[prop] === obj ? clone : obj[prop];//if property is reference to self, make clone reference clone, not the original object!
        }
    }
    return clone;
};

この関数は、オブジェクトの最初のレベルを複製します。元のオブジェクトのプロパティによって参照されているすべてのオブジェクトは引き続き共有されます。簡単な修正は、上記の関数を単純に再帰的に呼び出すことですが、すべてのレベルで循環参照という厄介な問題に対処する必要があります。

var circulars = {foo: bar};
circulars.circ1 = circulars;//simple circular reference, we can deal with this
circulars.mess = {gotcha: circulars};//circulars.mess.gotcha ==> circular reference, too
circulars.messier = {messiest: circulars.mess};//oh dear, this is hell

もちろん、これは最も一般的な状況ではありませんが、防御的にコードを書きたい場合は、多くの人が常に狂ったコードを書いているという事実を認めなければなりません...

例 2:

function CleanConstructor()
{};
CleanConstructor.prototype.method1 = function()
{
     //do stuff...
};
var foo = new CleanConstructor(), 
bar = new CleanConstructor);
console.log(foo === bar);//false, we have two separate instances
console.log(foo.method1 === bar.method1);//true: the function-object, referenced by method1 has only been created once.
//as opposed to:
function MessyConstructor()
{
    this.method1 = function()
    {//do stuff
    };
}
var foo = new MessyConstructor(),
bar = new MessyConstructor();
console.log(foo === bar);//false, as before
console.log(foo.method1 === bar.method1);//false! for each instance, a new function object is constructed, too: bad performance!

理論的には、最初のコンストラクターの宣言は面倒な方法よりも遅くなります: によって参照される関数オブジェクトはmethod1、単一のインスタンスが作成される前に作成されます。method12 番目の例では、コンストラクターが呼び出される場合を除き、は作成されません。しかし、欠点は非常に大きいnewです。最初の例のキーワードを忘れると、返される値はundefined. キーワードを省略すると、2 番目のコンストラクターはグローバル関数オブジェクトをnew作成し、もちろん呼び出しごとに新しい関数オブジェクトを作成します。実際にはアイドリングしているコンストラクター (およびプロトタイプ) があります...これにより、例 3が表示されます。

例 3:

var foo = [];//create an array - empty
console.log(foo[123]);//logs undefined.

では、舞台裏で何が起こっているか: object、 のインスタンスをfoo参照し、 Object プロトタイプから継承します ( を試してみてください)。したがって、当然のことながら、Array インスタンスは他のオブジェクトとほとんど同じように機能します。ArrayObject.getPrototypeOf(Array.prototype)

foo[123] ===> JS checks instance for property 123 (which is coerced to string BTW)
    || --> property not found @instance, check prototype (Array.prototype)
    ===========> Array.prototype.123 could not be found, check prototype
         ||
         ==========> Object.prototype.123: not found check prototype?
             ||
             =======>prototype is null, return undefined

言い換えれば、あなたが説明するようなチェーンは、あまりにも大げさでも珍しいものでもありません. これが JS のしくみです。つまり、物事を遅くすることを期待することは、脳が弱ってしまうことを期待するようなものです。そうです、考えすぎると疲れてしまう可能性がありますが、いつ休憩するかを知っているからです。プロトタイプ チェーンの場合と同じように: それらは素晴らしいですが、少し遅いことを知っておいてください。はい...

于 2013-01-15T10:46:11.277 に答える
1

完全にはわかりませんが、プログラミングするときは、機能を犠牲にすることなくコードをできるだけ小さくすることをお勧めします。私はそれを呼ぶのが好きminimalist codeです。

これは、コードを難読化する良い理由になる可能性があります。難読化は、メソッドと変数の名前を小さくすることでファイルのサイズを縮小し、リバースエンジニアリングを困難にし、ファイルサイズを縮小し、ダウンロードを高速化し、パフォーマンスを向上させる可能性があります。グーグルのjavascriptコードは非常に難読化されており、それがスピードに貢献しています。

したがって、JavaScriptでは、大きいほど良いとは限りません。コードを縮小する方法を見つけたら、それをすぐに実装します。これは、たとえ最小量であっても、パフォーマンスにメリットがあることがわかっているためです。

たとえばvar、関数の外部で変数が不要な関数でキーワードを使用すると、ガベージコレクションに役立ちます。これにより、変数をメモリに保持する場合に比べて、速度が大幅に向上します。

「1秒あたり数百万の操作」(ブレイズの言葉)を生成するこのようなライブラリを使用すると、わずかなパフォーマンスの向上により、顕著な/測定可能な違いが生じる可能性があります。

したがって、my.class.js「ミニマリストコード化」または何らかの方法で最適化されている可能性があります。varそれはキーワードでさえありえます。

これが多少役に立ったことを願っています。それが役に立たなかった場合は、良い答えを得ることができれば幸いです。

于 2013-01-15T00:48:46.000 に答える