JavaScriptでの単純な構造と複雑な構造のパフォーマンス

時折、JSコードの最適化に関する記事( これは最も人気のあるものの1つ )にぶつかりましたが、その中に悲惨な量の情報があると思いました。 リストされているのは、2〜3個のデザイン、1〜2個のブラウザ、そしてそれだけです。

sayingにもあるように、何かをうまくやりたいのであれば、自分でそれをしてください。
私はまず、最新の主要なブラウザーでさまざまな言語構成(最も基本的なものから始まる)の速度をテストし、パフォーマンスを要求するスクリプトで何をどのように使用するかについてのこの結論に基づいてテストすることにしました。

さて、結果が得られたので、一般的な使用のためにそれらを出してみませんか?

Upd: deerua habraiserが提供する結果のグラフを追加しました(表形式よりも情報の視覚的提示をよりよく知覚する人向け)

はじめに


したがって、テストマシンはP4 3GHz(デュアルコア)、1.5RAM、Vista 32ビットです。
一連のブラウザ-FF3、Opera 9.62、Chrom 1.0、IE7、IE8b2

IE8はオリジナルではなく、 IETesterプログラムを通じて立ち上げられたとすぐに言います
オリジナルとどれだけ一致するかはわかりませんが、少なくともIE7では、結果は非常に強く、方向が異なります。

誰かがIE6または他のブラウザをテストしたい場合-実際に歓迎します)

試験方法


以下で説明するすべての言語構成要素は、ループで100万回繰り返すことでテストされました。 特定の平均結果を明らかにするために、このアクションを各ブラウザーで数回繰り返しました(通常、現在のシステム負荷に応じて+ -2-5%の間で変動します)。 平均結果(ミリ秒単位)がテーブルに書き込まれました。

どこでも(特に明記されていない限り)、反転されたforループが最速として使用されました(var i = 1000000; i--;)。

ビジネスに取り掛かろう


サイクル


テスト/ブラウザFf3オペラクロムIE7IE8b2
サイクル
クラシックサイクル7110016238218
反転サイクル3434770118
whileループ3033770118


そこで、まず最初に、ループ自体をテストすることにしました(実行可能コードが含まれていない空のループのみ)。 なぜなら 私たちのテストの基礎を言う)
サイクルについてはすでに多くのことが書かれているため、結果は非常に予測可能です。
ご覧のとおり、反転forループ(var i = 1000000; i--;)は、従来のfor(var i = 0; i <1000000; i ++)の2倍以上の速度であり、これがさらなるテストで使用された理由です。
また、サイクルが実際に同じ構造であるため、サイクルの速度は実際には逆の場合と変わらないことに気付くことができます(作業の論理によると)。

配列とオブジェクトを操作する


テスト/ブラウザFf3オペラクロムIE7IE8b2
配列とオブジェクトの繰り返し
インデックスによる大きな配列(1M)の値の取得(完全な列挙の逆順)430170187901020
インデックスによる小さな配列(100)の値の取得12414618428515
オブジェクトのFor-inループ(1M)2020年21603853940035,400
逆ループでオブジェクト(1M)を反復処理する39017018745746
配列とオブジェクトの入力
array.lengthで配列を埋める(1M)190485822640865
ループのステップ(クラシックループ)の値を介して直接順序で配列を埋める(1M)200432752500760
サイクルステップ(反転サイクル)の値を逆順で配列に埋める(1M)118031012422702260
push()による配列の充填(1M)17611209844501186
サイクルステップ(クラシックサイクル)の値を介して直接順序でオブジェクト(1M)を埋める10803687424002205


この部分は、配列と代替オブジェクトの操作に専念しています。 このテストの代替オブジェクトとは、配列の置換としてのみ使用される(つまり、配列の要素以外のプロパティを含まない)オブジェクト(Object型)を意味します。 私はそのような代替品のスピードに興味がありました。

配列(またはオブジェクト)1M-要素数がそれぞれ1,000,000、ウェル、およびインデックス(キー)が0〜100万(より正確には999999)の配列(またはその代替)を意味します。

最初のテストでは、このような配列を逆ループで完全に反復します。 このようなもの:
var a = 0;
for(var i = 1000000; i--;){a = big_array [i];}

2番目のテストでは、ループを100万回繰り返しますが、各ステップで小さな配列(サイズが100要素)から同じ値を取得します。 このテストでは、配列のサイズが値を取得する速度にどの程度影響するかを確認しました。
テストが示したように、Chromeを除くすべてのブラウザーで、配列内の値の検索時間は、要素の数の増加とともに大幅に増加する可能性があります。

3番目のテストでは、配列置換オブジェクトで完全なfor-inループが作成されます。
for-inループはすべてのブラウザで非常に遅く(inverted forに比べて)、IEでは大量のデータに対して非常に遅いことがはっきりとわかります。 forループを回避できる配列またはオブジェクトの反復には使用しないでください(つまり、キーは数字で順序付けされています)。

4番目のテストは、big_arrayが配列ではなくオブジェクトであることを除いて、最初のテストに完全に類似しています。
ループを通過する速度は、配列または代替オブジェクトのどちらを持っているかにわずかにしか依存しないことがわかります。 具体的な違いはIE8のみですが、まだベータ版であるため、すべてが変更される可能性があります。


また、配列要素の範囲を選択するタスクに直面している場合、条件で必要な要素を選択してループ内の配列を並べ替えるよりも、ブラウザーでarray.slice()メソッドを使用する方が数倍高速であることに注意してください。 違いは非常に明白なので、テストには含めませんでした。

配列を列挙することに加えて、多くの場合、それらを埋める必要があるため、次の5つのテストはまさにそれです。
原則として、すべてがテーブルから明確になっている必要があります。




関数、オブジェクト、変数


関数、オブジェクト、変数
テスト/ブラウザFf3オペラクロムIE7IE8b2
現在のループ値で空の関数を呼び出す129270173100860
オブジェクトの作成(コンストラクターを介して2つのメソッドと1つのプロパティを作成)246019005931860011700
オブジェクトの作成(プロトタイプから2つのメソッドとコンストラクターを介して1つのプロパティを作成)12606366478304210
オブジェクトのプロパティ(独自のプロパティ)を取得する8414216406412
オブジェクトのプロパティの取得(プロトタイププロパティ)9014729日474474
オブジェクトプロパティの取得(プライベートvarプロパティの取得メソッド)2603543334301160
オブジェクトのインクリメンタルメソッドを呼び出します(これにより独自のプロパティをインクリメントします)3264606038101520
オブジェクトの増分メソッドの呼び出し(オブジェクトを明示的に指定することにより、独自のプロパティを増分します)3565206539851633
オブジェクトのインクリメンタルメソッドを呼び出す(プライベートvarプロパティをインクリメントする)4123703835301320



まず、ループ内で空の関数を呼び出すことをテストしました。
var f = function(){}
for(var i = 1,000,000; i--;){f();}

ほとんどのブラウザでは、関数の呼び出しはかなり安価な操作です(完全に空のループと比較して、100万回の呼び出しごとに数百ミリ秒しか追加されません)。
ただし、IEは、いつものように、不快な驚きを提示します。 多くのラッパー関数でコードをラップするのが好きな人は、考えるべきことがあります)

遅い関数呼び出しは、関数呼び出しに関連する他のテスト(オブジェクトの作成やメソッドの呼び出しなど)に悪影響を与えるため、IEでの実行時間がさらに長くなることに注意してください。

次の2つのテストは、100万個のオブジェクトの作成に当てられます。1つの場合はすべてのメソッドがコンストラクターを介して作成され、もう1つはプロトタイプを介して作成されます。
違いは明らかだと思います。

次の3つのテストは、オブジェクトのプロパティを呼び出します:独自のプロパティ(this.prop = valueで作成)、プロトタイププロパティ、およびプライベートプロパティ(コンストラクタ関数からクロージャで作成)。 明らかに、ゲッターを介して最後のオプションを取得します。
一般的に、結果は予測可能です-オブジェクト自体のプロパティをはるかに速く取得できます。

この後に、オブジェクトのインクリメンタルメソッドを呼び出す3つのテストが続きます(つまり、呼び出しごとにオブジェクトのプロパティを1つずつ増やすメソッド)。 実際、ここでの違いは、このプロパティがどのように正確に作成されたかということです(つまり、メソッドからオブジェクトのプロパティにアクセスする速度がテストされます)。
この場合、Firefoxを除くすべての場所でプライベートプロパティの変更率が高いことがはっきりとわかりますが、そのようなプロパティは同じタイプのすべてのオブジェクトに共通であり、外部からの読み取り時間は最悪です(ゲッター経由)。


テスト/ブラウザFf3オペラクロムIE7IE8b2
ifから8までのブランチを選択する8005006015001460
スイッチを介して可能な8つのブランチを選択する315334548681039
関数のハッシュから可能な8つのブランチを選択します6204008645201820



この表の最初の2つのテストについてコメントする必要はないと思います。3番目のテストについて詳しく説明します。
関数ハッシュは、スイッチの動作をエミュレートするオブジェクトです。
たとえば、次のようなコードがあります。
スイッチ(a){
ケース0:b = 6 + 2; 休憩;
ケース1:b = 8 * 3; 休憩;
}
次に、関数ハッシュは次のようになります。
ハッシュ= {
'0':関数(){return 6 + 2;}、
'1':関数(){return 8 * 3;}
}
b =ハッシュ[a]();

これらの単純なアクションでは、ハッシュはスイッチよりも悪い結果を示します(関数を呼び出す必要があるため)。 ただし、とにかくスイッチから関数を呼び出す場合、ハッシュはおそらくより高速になります。

一般的なブラウザ出力


繰り返しますが、すべてが予測可能です。 Chromeは自信を持っていくつかのparsecsによってリードし、firefoxとoperaは2番目と3番目の場所を互いに共有します(さまざまな方法で異なるテストで)、最後にロバは悲しげに織りますが、ベータロバは患者が生きている可能性が高いという希望を与えます死んだ。

おわりに


最初に注意したいのは、上記のテストはすべて合成テストであり、負荷はWebアプリケーションの通常よりも大幅に高くテストされることです(たとえば、100万の値の配列を扱う必要はあまりありません)。
したがって、特別に高負荷の合成テストのように扱う価値があります-つまり 特定のスクリプトにとって何が重要で、何が重要でないかを認識してください。

一方、今日100回呼び出されているメソッドは、明日すでに1万回呼び出される可能性があるため、この問題をonめるべきではありません。 そして、何かがどこかで減速し始めた場合、少なくともどの方向に「掘る」べきかについての理解があるでしょう)

Source: https://habr.com/ru/post/J49052/


All Articles