Leistung von Javascript Set vs. Array

88

Vielleicht, weil Sets für Javascript relativ neu sind, aber ich konnte weder in StackO noch anderswo einen Artikel finden, der über den Leistungsunterschied zwischen den beiden in Javascript spricht. Was ist also der Unterschied in Bezug auf die Leistung zwischen den beiden? Insbesondere beim Entfernen, Hinzufügen und Iterieren.

snowfrogdev
quelle
1
Sie können sie nicht austauschbar verwenden. Es macht also wenig Sinn, sie zu vergleichen.
Zerkms
Sprechen Sie über den Vergleich zwischen Setund []oder {}?
Eithed
2
Das Hinzufügen und Iterieren macht keinen großen Unterschied, das Entfernen und vor allem das Nachschlagen machen einen Unterschied.
Bergi
3
@ zerkms - streng genommen werden Arrays auch nicht geordnet, aber durch die Verwendung eines Index können sie so behandelt werden, als ob sie es wären. ;-) Die Wertefolge in einem Set wird in Einfügereihenfolge gehalten.
RobG

Antworten:

99

Ok, ich habe das Hinzufügen, Iterieren und Entfernen von Elementen aus einem Array und einer Menge getestet. Ich habe einen "kleinen" Test mit 10 000 Elementen und einen "großen" Test mit 100 000 Elementen durchgeführt. Hier sind die Ergebnisse.

Hinzufügen von Elementen zu einer Sammlung

Es scheint, dass die .pushArray-Methode ungefähr viermal schneller ist als die .addSet-Methode, unabhängig von der Anzahl der hinzugefügten Elemente.

Elemente in einer Sammlung durchlaufen und ändern

Für diesen Teil des Tests habe ich eine forSchleife verwendet, um über das Array for ofzu iterieren, und eine Schleife, um über die Menge zu iterieren. Wiederum war das Iterieren über das Array schneller. Diesmal scheint es exponentiell zu sein, da es bei den "kleinen" Tests doppelt so lange und bei den "großen" Tests fast viermal länger dauerte.

Elemente aus einer Sammlung entfernen

Hier wird es interessant. Ich habe eine Kombination aus einer forSchleife verwendet und .spliceeinige Elemente aus dem Array entfernt, und ich habe for ofund verwendet.delete zu entfernen, einige Elemente aus dem Set entfernt. Bei den "kleinen" Tests war es ungefähr dreimal schneller, Elemente aus dem Satz zu entfernen (2,6 ms gegenüber 7,1 ms), aber beim "großen" Test, bei dem 1955,1 ms benötigt wurden, um Elemente aus dem Array zu entfernen, änderten sich die Dinge drastisch Es dauerte 83,6 ms, um sie aus dem Set zu entfernen, 23-mal schneller.

Schlussfolgerungen

Bei 10.000 Elementen liefen beide Tests vergleichbare Zeiten (Array: 16,6 ms, Satz: 20,7 ms), aber bei 100.000 Elementen war der Satz der klare Gewinner (Array: 1974,8 ms, Satz: 83,6 ms), jedoch nur aufgrund des Entfernens Betrieb. Ansonsten war das Array schneller. Ich konnte nicht genau sagen, warum das so ist.

Ich habe mit einigen Hybridszenarien herumgespielt, in denen ein Array erstellt und gefüllt und dann in eine Gruppe konvertiert wurde, in der einige Elemente entfernt wurden. Die Gruppe wurde dann wieder in ein Array konvertiert. Obwohl dies eine viel bessere Leistung bietet als das Entfernen von Elementen im Array, überwiegt die zusätzliche Verarbeitungszeit, die für die Übertragung zu und von einem Satz erforderlich ist, die Vorteile des Auffüllens eines Arrays anstelle eines Satzes. Am Ende ist es schneller, nur mit einem Satz umzugehen. Dennoch ist es eine interessante Idee, dass, wenn man ein Array als Datenerfassung für einige Big Data ohne Duplikate verwendet, dies in Bezug auf die Leistung vorteilhaft sein kann, wenn jemals viele Elemente in einem entfernt werden müssen Operation, um das Array in eine Gruppe zu konvertieren, führen Sie die Entfernungsoperation aus und konvertieren Sie die Gruppe zurück in ein Array.

Array-Code:

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

Code einstellen:

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
quelle
1
Beachten Sie, dass die Werte eines Satzes standardmäßig eindeutig sind. Wenn also [1,1,1,1,1,1]ein Array die Länge 6 hätte, hätte ein Satz die Größe 1. Es sieht so aus, als würde Ihr Code aufgrund dieses Merkmals von Sätzen tatsächlich Sätze mit stark unterschiedlichen Größen von mehr als 100.000 Elementen in jedem Lauf generieren. Sie haben es wahrscheinlich nie bemerkt, weil Sie die Größe des Sets erst anzeigen, nachdem das gesamte Skript ausgeführt wurde.
KyleFarris
6
@KyleFarris Wenn ich mich nicht irre, wäre dies wahr, wenn Duplikate in der Menge vorhanden wären, wie in Ihrem Beispiel [1, 1, 1, 1, 1], aber da jedes Element in der Menge tatsächlich ein Objekt mit verschiedenen Eigenschaften ist, einschließlich eines zufällig aus einer Liste generierten Vor- und Nachnamens von Hunderten von möglichen Namen, einem zufällig generierten Alter, einem zufällig generierten Geschlecht und anderen zufällig generierten Attributen ... ist die Wahrscheinlichkeit, zwei identische Objekte in den Sets zu haben, gering bis gar nicht.
Snowfrogdev
3
In diesem Fall haben Sie tatsächlich Recht, da sich Sets anscheinend nicht von Objekten im Set unterscheiden. In der Tat könnten Sie sogar das gleiche exakte Objekt {foo: 'bar'}10.000x im Set haben und es hätte eine Größe von 10.000. Gleiches gilt für Arrays. Es scheint, dass es nur mit skalaren Werten (Zeichenfolgen, Zahlen, Booleschen Werten usw.) eindeutig ist.
KyleFarris
13
Sie könnten viele Male denselben exakten Inhalt eines Objekts {foo: 'bar'} im Set haben, aber nicht genau dasselbe Objekt (Referenz).
Hervorzuheben ist
15
Sie haben die Kennzahl als wichtigsten Grund für die Verwendung eines Sets vergessen, die 0 (1) -Suche. hasvs IndexOf.
Magnus
66

BEMERKUNGEN :

  • Set-Operationen können als Snapshots innerhalb des Ausführungsstroms verstanden werden.
  • Wir stehen nicht vor einem endgültigen Ersatz.
  • Die Elemente einer Set-Klasse haben keine zugänglichen Indizes.
  • Die Set-Klasse ist eine Array-Klassenergänzung , die in solchen Szenarien nützlich ist, in denen eine Sammlung gespeichert werden muss, auf die grundlegende Additions-, Lösch-, Überprüfungs- und Iterationsoperationen angewendet werden sollen.

Ich teile einige Leistungstests. Versuchen Sie, Ihre Konsole zu öffnen und den folgenden Code kopieren.

Erstellen eines Arrays (125000)

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

1. Suchen eines Index

Wir haben die has-Methode von Set mit Array indexOf verglichen:

Array / indexOf (0,281 ms) | Set / has (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. Hinzufügen eines neuen Elements

Wir vergleichen die Add- und Push-Methoden der Set- und Array-Objekte:

Array / Push (1,612 ms) | Einstellen / Hinzufügen (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. Ein Element löschen

Beim Löschen von Elementen müssen wir berücksichtigen, dass Array und Set nicht unter gleichen Bedingungen starten. Das Array verfügt nicht über eine native Methode, daher ist eine externe Funktion erforderlich.

Array / deleteFromArr (0,356 ms) | Einstellen / Entfernen (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' );

Lesen Sie den ganzen Artikel hier

Daniel Eduardo Delgado Diaz
quelle
4
Array.indexOf sollte Array.includes sein, damit sie gleichwertig sind. Ich bekomme sehr unterschiedliche Zahlen in Firefox.
Kagronick
2
Ich würde mich für den Vergleich von Object.includes vs. Set.has interessieren ...
Leopold Kristjansson
2
@LeopoldKristjansson Ich habe keinen Vergleichstest geschrieben, aber wir haben Timings in einer Produktionsstätte mit Arrays mit 24.000 Elementen durchgeführt und der Wechsel von Array.includes zu Set.has war eine enorme Leistungssteigerung!
Sedot
3

Meine Beobachtung ist, dass ein Set immer besser ist, wenn man zwei Fallstricke für große Arrays berücksichtigt:

a) Die Erstellung von Sets aus Arrays muss in einer forSchleife mit einer vorgespeicherten Länge erfolgen.

langsam (zB 18ms) new Set(largeArray)

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

b) Das Iterieren könnte auf die gleiche Weise erfolgen, da es auch schneller als eine for ofSchleife ist ...

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

für einen echten Leben Vergleich zu difference(), intersection(), union()und uniq()(+ ihre iteratee Begleiter etc.) mit 40.000 Elementen

Sebilasse
quelle
3

Screenshot der Benchmark-IterationFür den Iterationsteil Ihrer Frage habe ich kürzlich diesen Test durchgeführt und festgestellt, dass Set ein Array von 10.000 Elementen deutlich übertroffen hat (etwa das 10-fache der Vorgänge könnte im selben Zeitraum erfolgen). Und je nach Browser entweder geschlagen oder an Object.hasOwnProperty in einem Like-for-Like-Test verloren.

Sowohl Set als auch Object haben ihre "has" -Methode, die in einer amortisierten Form von O (1) ausgeführt wird. Abhängig von der Implementierung des Browsers kann ein einzelner Vorgang jedoch länger oder schneller dauern. Es scheint, dass die meisten Browser Schlüssel in Object schneller implementieren als Set.has (). Selbst Object.hasOwnProperty, das eine zusätzliche Überprüfung des Schlüssels enthält, ist zumindest für mich in Chrome v86 etwa 5% schneller als Set.has ().

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

Update: 11.11.2020: https://jsbench.me/irkhdxnoqa/2

Für den Fall, dass Sie Ihre eigenen Tests mit verschiedenen Browsern / Umgebungen ausführen möchten.


In ähnlicher Weise füge ich einen Benchmark zum Hinzufügen von Elementen zu einem Array hinzu, anstatt zu setzen und zu entfernen.

Zargold
quelle
4
Bitte verwenden Sie in Ihren Antworten keine Links (es sei denn, Sie sind mit einer offiziellen Bibliothek verknüpft), da diese Links möglicherweise beschädigt werden - wie in Ihrem Fall. Ihr Link ist 404.
Gil Epshtain
Ich habe einen Link verwendet, aber auch die Ausgabe kopiert, wenn sie verfügbar war. Es ist bedauerlich, dass sie ihre Verknüpfungsstrategie so schnell geändert haben.
Zargold
Der Beitrag wurde jetzt mit einem Screenshot und einer neuen JS-Performance-Website aktualisiert
Zargold
-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")

Diese drei Operationen an 10.000 Gegenständen gaben mir:

set: 7.787ms
array: 2.388ms
jessh
quelle
@Bergi das habe ich mir anfangs auch gedacht, aber es tut es.
Zerkms
1
@zerkms: Definiere "Arbeit" :-) Ja, das Array wird nach dem leer sein forEach, aber wahrscheinlich nicht so, wie du es erwartet hast. Wenn man ein vergleichbares Verhalten will, sollte es s.forEach(function(e) { s.clear(); })auch so sein.
Bergi
1
Nun, es macht etwas, nur nicht das, was beabsichtigt ist: Es löscht alle Elemente zwischen Index i und dem Ende. Das ist nicht vergleichbar mit dem, was deletedas Set macht.
Trincot
@Bergi oh richtig, es entfernt alles in nur 2 Iterationen. Mein Fehler.
Zerkms
4
In 1 Iteration. splice(0)leert ein Array.
Trincot