Javascript Set ve Dizi performansı


90

Belki Setler Javascript için nispeten yeni olduğu için, ancak StackO'da veya başka herhangi bir yerde, Javascript'teki ikisi arasındaki performans farkından bahseden bir makale bulamadım. Peki, performans açısından ikisi arasındaki fark nedir? Özellikle, kaldırma, ekleme ve yineleme söz konusu olduğunda.


1
Bunları birbirinin yerine kullanamazsınız. Bu yüzden onları karşılaştırmak çok az mantıklı.
zerkms

Setve []veya arasındaki karşılaştırmadan mı bahsediyorsun {}?
2016

2
Ekleme ve yineleme pek fark yaratmaz, kaldırmak ve en önemlisi arama bir fark yaratır.
Bergi


3
@ zerkms — kesinlikle, Array'ler de sıralı değildir, ancak bir indeksi kullanmaları, sanki öyleymiş gibi davranılmasını sağlar. ;-) Bir Kümedeki değerlerin sırası, ekleme sırasına göre tutulur.
RobG

Yanıtlar:


102

Tamam, hem bir diziden hem de bir kümeden öğe eklemeyi, yinelemeyi ve kaldırmayı test ettim. 10.000 öğe kullanarak "küçük" bir test ve 100.000 öğe kullanarak "büyük" bir test yaptım. Sonuçlar burada.

Bir koleksiyona öğe ekleme

Görünüşe .pushgöre, .addeklenen eleman sayısı ne olursa olsun dizi yöntemi set yönteminden yaklaşık 4 kat daha hızlıdır .

Bir koleksiyondaki öğeleri yineleme ve değiştirme

Testin bu kısmı foriçin dizi üzerinde yinelemek için bir for ofdöngü ve set üzerinde yinelemek için bir döngü kullandım. Yine, dizi üzerinde yineleme daha hızlıydı. Bu sefer, "küçük" testlerde iki kat daha uzun sürdüğü ve "büyük" testlerde neredeyse dört kat daha uzun sürdüğü için katlanarak olduğu görülüyor.

Bir koleksiyondan öğeleri kaldırma

Şimdi ilginçleşen yer burası. Bir fordöngü kombinasyonu .splicekullandım for ofve diziden .deletebazı elemanları kaldırmak için kullandım ve bazı elemanları setten kaldırdım. "Küçük" testler için, öğeleri setten çıkarmak yaklaşık üç kat daha hızlıydı (2,6 ms - 7,1 ms), ancak yalnızca diziden öğeleri kaldırmanın 1955,1 ms sürdüğü "büyük" test için işler büyük ölçüde değişti Bunları setten çıkarmak 83,6 ms sürdü, 23 kat daha hızlı.

Sonuçlar

10k öğelerde, her iki test de karşılaştırılabilir sürelerde çalıştı (dizi: 16,6 ms, set: 20,7 ms), ancak 100k öğelerle uğraşırken, küme açık kazanan oldu (dizi: 1974,8 ms, küme: 83,6 ms), ancak yalnızca kaldırma nedeniyle operasyon. Aksi takdirde dizi daha hızlıydı. Bunun tam olarak neden olduğunu söyleyemedim.

Bir dizinin oluşturulduğu ve doldurulduğu ve daha sonra bazı öğelerin kaldırılacağı bir kümeye dönüştürüldüğü bazı karma senaryolarla oynadım, set daha sonra bir diziye dönüştürülürdü. Bunu yapmak, dizideki öğeleri kaldırmaktan çok daha iyi bir performans sağlayacak olsa da, bir kümeye ve bir kümeden aktarım için gereken ek işlem süresi, bir dizi yerine diziyi doldurmanın kazançlarından daha ağır basar. Sonunda, sadece bir setle uğraşmak daha hızlıdır. Yine de, bir diziyi, yinelenenleri olmayan bazı büyük veriler için veri koleksiyonu olarak kullanmayı seçerseniz, bir arada birçok öğeyi kaldırmaya ihtiyaç duyulursa, performans açısından avantajlı olabilir. işlem, diziyi bir kümeye dönüştürmek, kaldırma işlemini gerçekleştirmek ve kümeyi yeniden diziye dönüştürmek için.

Dizi kodu:

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.")

Kodu ayarla:

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
Bir kümenin değerlerinin varsayılan olarak benzersiz olduğunu unutmayın. Öyleyse, [1,1,1,1,1,1]bir dizinin uzunluğu 6 olduğunda, bir setin boyutu 1 olacaktır. Görünüşe göre kodunuz, Setlerin bu özelliği nedeniyle her çalışmada 100.000 öğeden çılgınca farklı boyutlarda setler oluşturuyor olabilir. Muhtemelen hiç fark etmemişsinizdir çünkü tüm komut dosyası çalıştırılıncaya kadar kümenin boyutunu göstermiyorsunuz.
KyleFarris

6
@KyleFarris Yanılmıyorsam, sizin örneğinizde olduğu gibi kümede kopyalar olsaydı bu doğru olurdu [1, 1, 1, 1, 1], ancak kümedeki her öğe aslında bir ad ve soyadı da dahil olmak üzere çeşitli özelliklere sahip bir nesne olduğundan rastgele bir listeden oluşturuldu Yüzlerce olası isim, rastgele oluşturulmuş bir yaş, rastgele oluşturulmuş bir cinsiyet ve rastgele oluşturulmuş diğer özellikler ... setlerde iki özdeş nesneye sahip olma ihtimali hiç yok.
snowfrogdev

3
Aslında, bu durumda haklısınız çünkü öyle görünüyor ki Setler setteki nesnelerden farklı değil. Yani, aslında {foo: 'bar'}sette tam olarak aynı nesneye 10.000x sahip olabilirsiniz ve boyutu 10.000 olacaktır. Aynı şey diziler için de geçerli. Görünüşe göre sadece skaler değerlerle (dizeler, sayılar, mantıksallar vb.) Benzersizdir.
KyleFarris

13
Set içinde bir nesnenin aynı tam içeriğine{foo: 'bar'} birçok kez sahip olabilirsiniz, ancak tam olarak aynı nesneye (referans) sahip olamazsınız . Worth IMO ince farkı işaret
SimpleVar

16
Bir Set kullanmak için en önemli neden olan 0 (1) araması olan ölçüyü unuttunuz. hasvs IndexOf.
Magnus

67

GÖZLEMLER :

  • Set işlemleri, yürütme akışı içindeki anlık görüntüler olarak anlaşılabilir.
  • Kesin bir ikame değiliz.
  • Bir Set sınıfının öğelerinin erişilebilir dizinleri yoktur.
  • Set sınıfı , temel toplama, Silme, denetleme ve yineleme işlemlerini uygulayacağımız bir koleksiyonu depolamamız gereken senaryolarda yararlı olan bir Array sınıfı tamamlayıcısıdır.

Bazı performans testlerini paylaşıyorum. Konsolunuzu açmayı deneyin ve aşağıdaki kodu kopyalayın.

Bir dizi oluşturma (125000)

var n = 125000;
var arr = Array.apply( null, Array( n ) ).map( ( x, i ) => i );
console.info( arr.length ); // 125000

1. Bir Dizin Bulma

Set'in has yöntemini Array indexOf ile karşılaştırdık:

Dizi / indexOf (0,281 ms) | Ayarlandı / var (0,053 ms)

// 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. Yeni bir eleman eklemek

Sırasıyla Set ve Array nesnelerinin add ve push yöntemlerini karşılaştırıyoruz:

Dizi / itme (1,612 ms) | Ayarla / ekle (0,006 ms)

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. Bir öğeyi silme

Öğeleri silerken, Array ve Set'in eşit koşullar altında başlamadığını unutmamalıyız. Dizinin yerel bir yöntemi yoktur, bu nedenle harici bir işlev gereklidir.

Dizi / deleteFromArr (0,356 ms) | Ayarla / kaldır (0,019 ms)

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' );

Yazının tamamını buradan okuyun


4
Eşdeğer olmaları için Array.indexOf Array.includes olmalıdır. Firefox'ta çok farklı numaralar alıyorum.
kagronick

2
Object.includes ve Set.has karşılaştırmasıyla ilgilenirim ...
Leopold Kristjansson

2
@LeopoldKristjansson Bir karşılaştırma testi yazmadım, ancak 24k öğeli dizileri olan bir üretim sitesinde zamanlamalar yaptık ve Array.includes'dan Set.has'a geçiş yapmak muazzam bir performans artışı oldu!
sedot

4

Benim gözlemim, büyük diziler için akılda tutulan iki tuzakla bir Setin her zaman daha iyi olduğudur:

a) Dizilerden Setlerin oluşturulması, forönceden belirlenmiş uzunlukta bir döngü içinde yapılmalıdır .

yavaş (örneğin 18 ms) new Set(largeArray)

hızlı (örneğin 6ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }

b) Yineleme aynı şekilde yapılabilir çünkü for ofdöngüden daha hızlıdır ...

Bkz. Https://jsfiddle.net/0j2gkae7/5/

gerçek bir hayat karşılaştırma için difference(), intersection(), union()ve uniq()40.000 öğelerle (+ onların iteratee arkadaşları vs.)


3

Karşılaştırmalı Yinelemenin ekran görüntüsüSorunuzun yineleme kısmı için, yakın zamanda bu testi çalıştırdım ve Set'in 10.000 öğelik bir Diziden çok daha iyi performans gösterdiğini buldum (işlemler aynı zaman diliminde yaklaşık 10 kat olabilir). Ve tarayıcıya bağlı olarak, benzer bir testte Object.hasOwnProperty'yi yener veya kaybeder.

Hem Küme hem de Nesne, O (1) olarak amortize edilmiş gibi görünen "has" yöntemine sahiptir, ancak tarayıcının uygulamasına bağlı olarak tek bir işlem daha uzun veya daha hızlı sürebilir. Görünüşe göre tarayıcıların çoğu Object'te anahtarı Set.has () 'dan daha hızlı uyguluyor. Anahtar üzerinde ek bir kontrol içeren Object.hasOwnProperty bile en azından Chrome v86'da Set.has () 'dan yaklaşık% 5 daha hızlıdır.

https://jsperf.com/set-has-vs-object-hasownproperty-vs-array-includes/1

Güncelleme: 11/11/2020: https://jsbench.me/irkhdxnoqa/2

Farklı tarayıcılar / ortamlar ile kendi testlerinizi çalıştırmak istemeniz durumunda.


Benzer şekilde, bir diziye öğeler eklemek ve kümeyi kaldırmak için bir ölçüt ekleyeceğim.


4
Sizin durumunuzda olduğu gibi, bu bağlantılar kopabileceğinden, lütfen yanıtlarınızda bağlantılar kullanmayın (resmi bir kütüphaneye bağlanmadıkça). Bağlantınız 404.
Gil Epshtain

Bir bağlantı kullandım, ancak çıktıyı kullanılabilir olduğunda da kopyaladım. Bağlama stratejilerini bu kadar çabuk değiştirmeleri talihsiz bir durum.
Zargold


0

Sadece Özellik Araması, az veya sıfır yazma

Mülk arama ana endişenizse, işte bazı rakamlar.

JSBench testleri https://jsbench.me/3pkjlwzhbr/1

Dizi
  • for döngü
  • for döngü (ters)
  • array.includes(target)
Ayarlamak
  • set.has(target)
Nesne
  • obj.hasOwnProperty(target)
  • target in obj <-% 1.29 daha yavaş
  • obj[target] <- en hızlı
Harita
  • map.has(target) <-% 2.94 daha yavaş
Elde edilen sonuçlar , Ocak 2021, Chrome 87

görüntü açıklamasını buraya girin

Diğer tarayıcılardan alınan sonuçlar memnuniyetle karşılanır, lütfen bu yanıtı güncelleyin. Güzel bir ekran görüntüsü almak için bu elektronik tabloyu
kullanabilirsiniz .

JSBench testi, Zargold'un cevabından ayrıldı.


-5
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 eşyalar üzerindeki bu üç operasyon bana verdi:

set: 7.787ms
array: 2.388ms

@Bergi başlangıçta ben de öyle düşünmüştüm, ama öyle.
zerkms

1
@zerkms: "iş" i tanımlayın :-) Evet, dizi bundan sonra boş olacak forEach, ancak muhtemelen beklediğiniz şekilde olmayacaktır. Karşılaştırılabilir davranışlar istiyorsa, öyle olmalıdır s.forEach(function(e) { s.clear(); }).
Bergi

1
Pekala, sadece amaçlanan bir şey değil: indeks i ile son arasındaki tüm öğeleri siler . Bu delete, Sette yaptıklarıyla karşılaştırılamaz .
trincot

@Bergi oh doğru, her şeyi sadece 2 yinelemede kaldırır. Benim hatam.
zerkms

4
1 yinelemede. splice(0)bir diziyi boşaltır.
trincot
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.