セットはJavascriptに比較的新しいためかもしれませんが、StackOやその他の場所で、Javascriptの2つのパフォーマンスの違いについて説明している記事を見つけることができませんでした。では、パフォーマンスの観点から、2つの違いは何ですか?具体的には、削除、追加、反復に関してです。
セットはJavascriptに比較的新しいためかもしれませんが、StackOやその他の場所で、Javascriptの2つのパフォーマンスの違いについて説明している記事を見つけることができませんでした。では、パフォーマンスの観点から、2つの違いは何ですか?具体的には、削除、追加、反復に関してです。
Set
と[]
か{}
?
回答:
わかりました。配列とセットの両方から要素を追加、反復、削除することをテストしました。10000個の要素を使用して「小さい」テストを実行し、100000個の要素を使用して「大きい」テストを実行しました。結果は次のとおりです。
追加する要素の数に関係なく、.push
arrayメソッドは.add
setメソッドよりも約4倍高速であるように見えます。
テストのこの部分では、for
ループを使用して配列を反復処理し、ループを使用for of
してセットを反復処理しました。繰り返しになりますが、配列の反復処理は高速でした。今回は、「小さい」テストでは2倍、「大きい」テストではほぼ4倍の時間がかかったため、指数関数的に見えるようになります。
今、これはそれが面白くなるところです。for
ループと.splice
配列からいくつかの要素を削除するためにループを組み合わせて使用し、セットからいくつかの要素を削除するために使用for of
し.delete
ました。「小さい」テストの場合、セットからアイテムを削除する方が約3倍高速でした(2.6ミリ秒対7.1ミリ秒)が、配列からアイテムを削除するのに1955.1ミリ秒かかった「大きい」テストでは、状況が大幅に変わりました。セットからそれらを削除するのに83.6ミリ秒かかり、23倍速くなりました。
10k要素では、両方のテストが同等の時間(配列:16.6 ms、セット:20.7 ms)で実行されましたが、100k要素を処理する場合、セットが明確な勝者でした(配列:1974.8 ms、セット:83.6 ms)が、削除したためです。操作。それ以外の場合、アレイは高速でした。それがなぜなのか正確には言えませんでした。
配列が作成されて入力され、いくつかの要素が削除されるセットに変換された後、セットが配列に再変換される、いくつかのハイブリッドシナリオを試してみました。これを行うと、配列内の要素を削除するよりもはるかに優れたパフォーマンスが得られますが、セットとの間で転送するために必要な追加の処理時間は、セットではなく配列にデータを入力することの利点を上回ります。結局、セットだけを扱う方が速いです。それでも、重複のないビッグデータのデータコレクションとして配列を使用することを選択した場合、1つの要素から多くの要素を削除する必要がある場合は、パフォーマンスの面で有利になる可能性があるというのは興味深い考えです。操作。配列をセットに変換するには、削除操作を実行して、セットを配列に戻します。
配列コード:
var timer = function(name) {
var start = new Date();
return {
stop: function() {
var end = new Date();
var time = end.getTime() - start.getTime();
console.log('Timer:', name, 'finished in', time, 'ms');
}
}
};
var getRandom = function(min, max) {
return Math.random() * (max - min) + min;
};
var lastNames = ['SMITH', 'JOHNSON', 'WILLIAMS', 'JONES', 'BROWN', 'DAVIS', 'MILLER', 'WILSON', 'MOORE', 'TAYLOR', 'ANDERSON', 'THOMAS'];
var genLastName = function() {
var index = Math.round(getRandom(0, lastNames.length - 1));
return lastNames[index];
};
var sex = ["Male", "Female"];
var genSex = function() {
var index = Math.round(getRandom(0, sex.length - 1));
return sex[index];
};
var Person = function() {
this.name = genLastName();
this.age = Math.round(getRandom(0, 100))
this.sex = "Male"
};
var genPersons = function() {
for (var i = 0; i < 100000; i++)
personArray.push(new Person());
};
var changeSex = function() {
for (var i = 0; i < personArray.length; i++) {
personArray[i].sex = genSex();
}
};
var deleteMale = function() {
for (var i = 0; i < personArray.length; i++) {
if (personArray[i].sex === "Male") {
personArray.splice(i, 1)
i--
}
}
};
var t = timer("Array");
var personArray = [];
genPersons();
changeSex();
deleteMale();
t.stop();
console.log("Done! There are " + personArray.length + " persons.")
セットコード:
var timer = function(name) {
var start = new Date();
return {
stop: function() {
var end = new Date();
var time = end.getTime() - start.getTime();
console.log('Timer:', name, 'finished in', time, 'ms');
}
}
};
var getRandom = function (min, max) {
return Math.random() * (max - min) + min;
};
var lastNames = ['SMITH','JOHNSON','WILLIAMS','JONES','BROWN','DAVIS','MILLER','WILSON','MOORE','TAYLOR','ANDERSON','THOMAS'];
var genLastName = function() {
var index = Math.round(getRandom(0, lastNames.length - 1));
return lastNames[index];
};
var sex = ["Male", "Female"];
var genSex = function() {
var index = Math.round(getRandom(0, sex.length - 1));
return sex[index];
};
var Person = function() {
this.name = genLastName();
this.age = Math.round(getRandom(0,100))
this.sex = "Male"
};
var genPersons = function() {
for (var i = 0; i < 100000; i++)
personSet.add(new Person());
};
var changeSex = function() {
for (var key of personSet) {
key.sex = genSex();
}
};
var deleteMale = function() {
for (var key of personSet) {
if (key.sex === "Male") {
personSet.delete(key)
}
}
};
var t = timer("Set");
var personSet = new Set();
genPersons();
changeSex();
deleteMale();
t.stop();
console.log("Done! There are " + personSet.size + " persons.")
[1,1,1,1,1,1]
配列の長さが6の場合、セットのサイズは1になります。このセットの特性により、コードは実際には、実行ごとにサイズが100,000アイテムよりも大きく異なるサイズのセットを生成する可能性があります。スクリプト全体が実行されるまでセットのサイズが表示されないため、おそらく気付かないでしょう。
[1, 1, 1, 1, 1]
ますが、セット内の各アイテムは実際にはリストからランダムに生成された姓名を含むさまざまなプロパティを持つオブジェクトであるためです何百もの可能な名前、ランダムに生成された年齢、ランダムに生成された性別、およびその他のランダムに生成された属性...セットに2つの同一のオブジェクトがある可能性はほとんどありません。
{foo: 'bar'}
は、セットに同じ正確なオブジェクト10,000xを含めることもでき、サイズは10,000になります。アレイについても同じことが言えます。スカラー値(文字列、数値、ブール値など)でのみ一意のようです。
{foo: 'bar'}
何度も含めることができますが、まったく同じオブジェクト(参照)を含めることはできません。IMO微妙な違いを指摘する価値
has
対IndexOf
。
観察:
- セット操作は、実行ストリーム内のスナップショットとして理解できます。
- 私たちは決定的な代用品の前ではありません。
- Setクラスの要素には、アクセス可能なインデックスがありません。
- セットクラスは配列クラスの補足であり、基本的な加算、削除、チェック、および反復操作を適用するコレクションを格納する必要があるシナリオで役立ちます。
パフォーマンスのテストをいくつか共有します。コンソールを開いて、以下のコードをコピーして貼り付けてみてください。
配列の作成(125000)
var n = 125000;
var arr = Array.apply( null, Array( n ) ).map( ( x, i ) => i );
console.info( arr.length ); // 125000
1.インデックスの検索
SetのhasメソッドをArrayindexOfと比較しました。
Array / indexOf(0.281ms)| セット/持っている(0.053ms)
// Helpers
var checkArr = ( arr, item ) => arr.indexOf( item ) !== -1;
var checkSet = ( set, item ) => set.has( item );
// Vars
var set, result;
console.time( 'timeTest' );
result = checkArr( arr, 123123 );
console.timeEnd( 'timeTest' );
set = new Set( arr );
console.time( 'timeTest' );
checkSet( set, 123123 );
console.timeEnd( 'timeTest' );
2.新しい要素を追加する
SetオブジェクトとArrayオブジェクトのaddメソッドとpushメソッドをそれぞれ比較します。
アレイ/プッシュ(1.612ms)| 設定/追加(0.006ms)
console.time( 'timeTest' );
arr.push( n + 1 );
console.timeEnd( 'timeTest' );
set = new Set( arr );
console.time( 'timeTest' );
set.add( n + 1 );
console.timeEnd( 'timeTest' );
console.info( arr.length ); // 125001
console.info( set.size ); // 125001
3.要素の削除
要素を削除するときは、ArrayとSetが同じ条件下で開始されないことに注意する必要があります。配列にはネイティブメソッドがないため、外部関数が必要です。
Array / deleteFromArr(0.356ms)| セット/削除(0.019ms)
var deleteFromArr = ( arr, item ) => {
var i = arr.indexOf( item );
i !== -1 && arr.splice( i, 1 );
};
console.time( 'timeTest' );
deleteFromArr( arr, 123123 );
console.timeEnd( 'timeTest' );
set = new Set( arr );
console.time( 'timeTest' );
set.delete( 123123 );
console.timeEnd( 'timeTest' );
ここで記事全文を読む
私の観察では、大きな配列の2つの落とし穴を念頭に置いて、セットの方が常に優れています。
a)配列からのセットの作成はfor
、事前にキャッシュされた長さのループで実行する必要があります。
遅い(例:18ms) new Set(largeArray)
高速(例:6ms)
const SET = new Set();
const L = largeArray.length;
for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }
b)for of
ループよりも高速であるため、同じ方法で反復を行うことができます。
https://jsfiddle.net/0j2gkae7/5/を参照してください
現実の比較のために
difference()
、intersection()
、union()
およびuniq()
40.000の要素を持つ(+そのiterateeの仲間など)
あなたの質問の反復部分について、私は最近このテストを実行し、Setが10,000アイテムの配列をはるかに上回っていることを発見しました(操作は同じ時間枠で約10倍発生する可能性があります)。そして、ブラウザに応じて、同様のテストでObject.hasOwnPropertyに勝つか負けます。
SetとObjectはどちらも、O(1)に償却されているように見える「has」メソッドを実行しますが、ブラウザーの実装によっては、1回の操作にかかる時間が長くなることも速くなることもあります。ほとんどのブラウザは、Set.has()よりも速くObjectにキーを実装しているようです。キーの追加チェックを含むObject.hasOwnPropertyでさえ、少なくともChrome v86ではSet.has()よりも約5%高速です。
https://jsperf.com/set-has-vs-object-hasownproperty-vs-array-includes/1
更新:2020年11月11日:https://jsbench.me/irkhdxnoqa/2
さまざまなブラウザ/環境で独自のテストを実行する場合。
同様に、配列にアイテムを追加するためのベンチマークと、セットおよび削除するためのベンチマークを追加します。
プロパティルックアップがあなたの主な関心事であるならば、ここにいくつかの数字があります。
JSBenchテストhttps://jsbench.me/3pkjlwzhbr/1
アレイfor
ループfor
ループ(反転)array.includes(target)
set.has(target)
obj.hasOwnProperty(target)
target in obj
<-1.29%遅いobj[target]
<-最速map.has(target)
<-2.94%遅い他のブラウザからの結果は大歓迎です。この回答を更新してください。このスプレッドシートを
使用して、素敵なスクリーンショットを作成できます。
JSBenchテストはZargoldの回答から分岐しました。
console.time("set")
var s = new Set()
for(var i = 0; i < 10000; i++)
s.add(Math.random())
s.forEach(function(e){
s.delete(e)
})
console.timeEnd("set")
console.time("array")
var s = new Array()
for(var i = 0; i < 10000; i++)
s.push(Math.random())
s.forEach(function(e,i){
s.splice(i)
})
console.timeEnd("array")
10Kアイテムに対するこれらの3つの操作により、次のことがわかりました。
set: 7.787ms
array: 2.388ms
forEach
が、おそらく期待した方法ではありません。同等の動作が必要な場合は、それも必要s.forEach(function(e) { s.clear(); })
です。
delete
、セットで行うこととは比較されません。
splice(0)
配列を空にします。