Javascript Set vs. kinerja Array

90

Mungkin karena Sets relatif baru di Javascript tetapi saya belum dapat menemukan artikel, di StackO atau di mana pun, yang berbicara tentang perbedaan kinerja antara keduanya di Javascript. Lantas, apa perbedaan dari segi performa di antara keduanya? Secara khusus, ketika menyangkut penghapusan, penambahan, dan pengulangan.

snowfrogdev
sumber
1
Anda tidak dapat menggunakannya secara bergantian. Jadi sangat tidak masuk akal untuk membandingkannya.
zerkms
apakah Anda berbicara tentang perbandingan antara Setdan []atau {}?
eithed
2
Menambahkan dan mengulang tidak membuat banyak perbedaan, menghapus dan - yang terpenting - pencarian memang membuat perbedaan.
Bergi
3
@ zerkms — tepatnya, Array juga tidak diurutkan, tetapi penggunaan indeksnya memungkinkan mereka diperlakukan seolah-olah ada. ;-) Urutan nilai dalam sebuah Set disimpan dalam urutan penyisipan.
RobG

Jawaban:

102

Oke, saya telah menguji menambahkan, mengulang, dan menghapus elemen dari array dan set. Saya menjalankan tes "kecil", menggunakan 10.000 elemen dan tes "besar", menggunakan 100.000 elemen. Berikut hasilnya.

Menambahkan elemen ke koleksi

Tampaknya .pushmetode array sekitar 4 kali lebih cepat daripada .addmetode set, tidak peduli jumlah elemen yang ditambahkan.

Iterasi dan modifikasi elemen dalam koleksi

Untuk bagian pengujian ini saya menggunakan forloop untuk mengulang array dan for ofloop untuk mengulang set. Sekali lagi, pengulangan array lebih cepat. Kali ini akan terlihat secara eksponensial sehingga memakan waktu dua kali lebih lama selama tes "kecil" dan hampir empat kali lebih lama selama tes "besar".

Menghapus elemen dari koleksi

Sekarang di sinilah menjadi menarik. Saya menggunakan kombinasi forloop dan .spliceuntuk menghapus beberapa elemen dari array dan saya menggunakan for ofdan .deleteuntuk menghapus beberapa elemen dari set. Untuk pengujian "kecil", sekitar tiga kali lebih cepat untuk menghapus item dari set (2,6 md vs 7,1 md) tetapi banyak hal berubah secara drastis untuk pengujian "besar" di mana dibutuhkan 1955,1 md untuk menghapus item dari larik sementara itu hanya butuh 83,6 ms untuk menghapusnya dari set, 23 kali lebih cepat.

Kesimpulan

Pada 10k elemen, kedua tes berjalan dengan waktu yang sebanding (array: 16.6 ms, set: 20.7 ms) tetapi ketika berhadapan dengan elemen 100k, set adalah pemenang yang jelas (array: 1974.8 ms, set: 83.6 ms) tetapi hanya karena penghapusan operasi. Jika tidak, susunannya lebih cepat. Saya tidak bisa mengatakan dengan tepat mengapa itu terjadi.

Saya bermain-main dengan beberapa skenario hybrid di mana sebuah array dibuat dan diisi dan kemudian diubah menjadi satu set di mana beberapa elemen akan dihapus, set tersebut kemudian akan diubah kembali menjadi sebuah array. Meskipun melakukan ini akan memberikan kinerja yang jauh lebih baik daripada menghapus elemen dalam larik, waktu pemrosesan tambahan yang diperlukan untuk mentransfer ke dan dari suatu set lebih besar daripada keuntungan dari mengisi larik daripada satu set. Pada akhirnya, lebih cepat hanya menangani satu set. Namun, ini adalah ide yang menarik, bahwa jika seseorang memilih untuk menggunakan array sebagai pengumpulan data untuk beberapa data besar yang tidak memiliki duplikat, itu bisa menjadi kinerja yang menguntungkan, jika ada kebutuhan untuk menghapus banyak elemen dalam satu operasi, untuk mengonversi larik menjadi satu set, melakukan operasi penghapusan, dan mengonversi set kembali menjadi larik.

Kode array:

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

Atur kode:

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

snowfrogdev
sumber
1
Perlu diingat, nilai suatu kumpulan unik secara default. Jadi, jika [1,1,1,1,1,1]untuk sebuah larik akan memiliki panjang 6, satu set akan memiliki ukuran 1. Sepertinya kode Anda sebenarnya dapat menghasilkan set dengan ukuran yang sangat berbeda dari 100.000 item dalam ukuran pada setiap proses karena sifat Set ini. Anda mungkin tidak pernah menyadarinya karena Anda tidak menampilkan ukuran set hingga seluruh skrip dijalankan.
KyleFarris
6
@KyleFarris Kecuali jika saya salah, ini akan benar jika ada duplikat di set, seperti dalam contoh Anda [1, 1, 1, 1, 1], tetapi karena setiap item dalam set sebenarnya adalah objek dengan berbagai properti termasuk nama depan dan nama belakang yang dihasilkan secara acak dari daftar dari ratusan kemungkinan nama, usia yang dibuat secara acak, jenis kelamin yang dibuat secara acak, dan atribut yang dihasilkan secara acak lainnya ... kemungkinan memiliki dua objek yang identik dalam set sangat kecil.
snowfrogdev
3
Sebenarnya, Anda benar dalam kasus ini karena tampaknya Sets tidak benar-benar membedakan dari objek dalam set. Jadi, Anda bahkan dapat memiliki objek yang sama persis {foo: 'bar'}10.000x dalam himpunan dan akan memiliki ukuran 10.000. Hal yang sama berlaku untuk array. Tampaknya itu hanya unik dengan nilai skalar (string, angka, boolean, dll ..).
KyleFarris
13
Anda dapat memiliki konten objek yang persis sama {foo: 'bar'}berkali-kali dalam Kumpulan, tetapi bukan objek yang sama persis (referensi).
Penting untuk
16
Anda lupa ukuran, alasan terpenting untuk menggunakan Set, pencarian 0 (1). hasvs IndexOf.
Magnus
67

OBSERVASI :

  • Operasi set dapat dipahami sebagai snapshot dalam aliran eksekusi.
  • Kami tidak sebelum pengganti definitif.
  • Elemen kelas Set tidak memiliki indeks yang bisa diakses.
  • Kelas set adalah pelengkap kelas Array , berguna dalam skenario di mana kita perlu menyimpan koleksi untuk menerapkan operasi penambahan, Penghapusan, pemeriksaan, dan iterasi dasar.

Saya membagikan beberapa tes kinerja. Coba buka konsol Anda dan tempelkan kode di bawah ini.

Membuat array (125000)

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

1. Menemukan Indeks

Kami membandingkan metode Set dengan Array indexOf:

Array / indexOf (0,281 md) | Set / memiliki (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. Menambahkan elemen baru

Kami membandingkan metode add dan push dari objek Set dan Array masing-masing:

Larik / dorong (1,612 md) | Setel / tambahkan (0,006 md)

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. Menghapus sebuah elemen

Saat menghapus elemen, kita harus ingat bahwa Array dan Set tidak dimulai dalam kondisi yang sama. Array tidak memiliki metode asli, jadi diperlukan fungsi eksternal.

Larik / deleteFromArr (0,356 md) | Setel / hapus (0,019 md)

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

Baca artikel lengkapnya di sini

Daniel Eduardo Delgado Diaz
sumber
4
Array.indexOf haruslah Array.includes agar setara. Saya mendapatkan nomor yang sangat berbeda di Firefox.
kagronick
2
Saya akan tertarik pada Object. Termasuk vs. Set. Memiliki perbandingan ...
Leopold Kristjansson
2
@LeopoldKristjansson Saya tidak menulis tes perbandingan, tetapi kami melakukan pengaturan waktu di situs produksi dengan array dengan 24k item dan beralih dari Array.includes ke Set. Telah meningkatkan performa yang luar biasa!
sedot
4

Pengamatan saya adalah bahwa Set selalu lebih baik dengan dua perangkap untuk array besar:

a) Pembuatan Set dari Array harus dilakukan dalam satu forlingkaran dengan panjang yang telah ditentukan sebelumnya.

lambat (mis. 18ms) new Set(largeArray)

cepat (mis. 6ms) const SET = new Set(); const L = largeArray.length; for(var i = 0; i<L; i++) { SET.add(largeArray[i]) }

b) Iterasi bisa dilakukan dengan cara yang sama karena juga lebih cepat dari for ofloop ...

Lihat https://jsfiddle.net/0j2gkae7/5/

untuk kehidupan perbandingan nyata untuk difference(), intersection(), union()dan uniq()(+ sahabat iteratee mereka dll) dengan 40.000 elemen

sebilasse
sumber
3

Tangkapan layar dari Iterasi berbandingUntuk bagian iterasi dari pertanyaan Anda, saya baru-baru ini menjalankan tes ini dan menemukan bahwa Set jauh mengungguli Array 10.000 item (sekitar 10x operasi dapat terjadi dalam jangka waktu yang sama). Dan tergantung pada browser apakah mengalahkan atau kalah dari Object.hasOwnProperty dalam tes sejenisnya.

Baik Set dan Object memiliki metode "has" yang bekerja dalam apa yang tampaknya diamortisasi ke O (1), tetapi tergantung pada implementasi browser, operasi tunggal bisa memakan waktu lebih lama atau lebih cepat. Tampaknya sebagian besar browser mengimplementasikan kunci di Object lebih cepat daripada Set.has (). Bahkan Object.hasOwnProperty yang menyertakan pemeriksaan tambahan pada kunci sekitar 5% lebih cepat dari Set.has () setidaknya bagi saya di Chrome v86.

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

Pembaruan: 11/11/2020: https://jsbench.me/irkhdxnoqa/2

Jika Anda ingin menjalankan pengujian Anda sendiri dengan browser / lingkungan yang berbeda.


Demikian pula saya akan menambahkan patokan untuk menambahkan item ke array vs set dan menghapus.

Zargold
sumber
4
Mohon jangan gunakan tautan dalam jawaban Anda (kecuali ditautkan ke perpustakaan resmi) karena tautan ini dapat rusak - seperti yang terjadi pada kasus Anda. Tautan Anda adalah 404.
Gil Epshtain
Saya menggunakan tautan tetapi juga menyalin hasilnya ketika tersedia. Sangat disayangkan mereka mengubah strategi penautan mereka dengan sangat cepat.
Zargold
Memperbarui pos sekarang dengan tangkapan layar dan situs web kinerja JS baru: jsbench.me
Zargold
0

Hanya Pencarian Properti, sedikit atau nol tulis

Jika pencarian properti adalah perhatian utama Anda, berikut beberapa angkanya.

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

Himpunan
  • for lingkaran
  • for loop (terbalik)
  • array.includes(target)
Set
  • set.has(target)
Obyek
  • obj.hasOwnProperty(target)
  • target in obj <- 1,29% lebih lambat
  • obj[target] <- tercepat
Peta
  • map.has(target) <- 2.94% lebih lambat
Hasil dari Januari 2021, Chrome 87

masukkan deskripsi gambar di sini

Hasil dari browser lain dipersilahkan, perbarui jawaban ini.
Anda dapat menggunakan spreadsheet ini untuk membuat tangkapan layar yang bagus.

Tes JSBench bercabang dari jawaban Zargold.

Qwerty
sumber
-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")

Tiga operasi pada 10 ribu item itu memberi saya:

set: 7.787ms
array: 2.388ms
jessh
sumber
@Bergi itulah yang saya pikirkan pada awalnya juga, tetapi ternyata.
zerkms
1
@zerkms: Definisikan "kerja" :-) Ya, array akan kosong setelah forEach, tapi mungkin tidak seperti yang Anda harapkan. Jika seseorang menginginkan perilaku yang sebanding, seharusnya s.forEach(function(e) { s.clear(); })juga demikian.
Bergi
1
Yah, itu melakukan sesuatu, hanya bukan apa yang dimaksudkan: itu menghapus semua elemen antara indeks i dan akhir. Itu tidak sebanding dengan apa yang deletedilakukan di Set.
trincot
@Bergi oh benar, ini menghapus semuanya hanya dalam 2 iterasi. Salahku.
zerkms
4
Dalam 1 iterasi. splice(0)mengosongkan array.
trincot