Aktuelles Update: Für Objects kann noch schneller sein:
const keys = Object.keys(haploidCells); const len = keys.length;
for (let i = 0; i < len; i++) {
const key = keys[i];
const cell = haploidCells[key];
}
Das bringt die loops nochmal von 120ms auf 19ms runter - der Aufwand lohnt sich also wenn man Record
Großes Aber: Das gilt für Objekte mit hundertausenden Einträgen - denn jedesmal keys und length setzen hat auch overhead.
Bei 5-20 Einträgen ist der Unterschied schon auf 10% zusammengeschrumpft. Mit random einträgen zw. 5 und 20 (üblich in meinen Fällen) ist der Unterschied nahe Null.
Daher gilt nach wie vor .............
Sooo, nach 3 Tagen Benchmarken und Browser-Wechsel, etc. gibt es eine Lösung die für alle Browser optimal ist und mit 2 Regeln auskommt:
Wer seine Typen selbst definiert oder Record<T,T> nutzt wird sich keine prototypes ins Haus holen. Man erhält Key und Value. Noch schneller geht es mit Map<T,T> statt Records
Man erhält Index und Value - Key gibt es in Arrays ohnehin nicht.
Das wars. Higher-order-functions on arrays machen den Speed nie, wer Array
Brave und Firefox haben unterschiedliche Werte für Object.keys/values/entries, aber in jedem Fall ist for ... in die schnellste Lösung.
Ich lasse den Beitrag trotzdem so stehen. Wer typsicher und threadsafe schreibt und vom Clienten holt wozu er fähig ist bekommt mittlerweile mit TS und Esbuild Möglichkeit nahezu an der Hardware Code zu schicken - das fetzt schon.
Airbnb style guide wird einem erklären, man möge keine Iteratoren verwenden, da sie den state des Objektes ändern (tun Sie überhaupt nicht), aber ehrlichgesagt sind die higher-order-functions trotzdem mit overhead verbunden und wirklich lesbar ist das auch nicht.
Das benutzt sowieso keine ernstzunehmende Library - systemweit den prototypen zu ergänzen ist völlig unnötig und wenn man das wirklich braucht kann man es mit .enumerable = false auch aus for ... in raushalten.
Ich finde bei den Benchmarks mega interessant, dass Firefox an sich viel schneller ist - meine Usecases geben das aber nicht immer her, ThreeJS oder 2D-Canvas sind in Chrome doch irgendwie fixer. Es liegt wohl am besseren Multi-Threading in Chrome, d.h. wer selbst die Threads holt dürfte seine Page auf FF und Chrome gleichermaßen fix zum Kunden bekommen.
Wer sauberen Code schreibt und for-loops nutzt, seine Typen klar deklariert ist mit Firefox wahrscheinlich besser dran.
Das ist eigentlich ganz schön so, denn ehrlichgesagt kann man sich Chrome wirklich nur für den Speed ins Haus holen.
Esbuild TS -> JS. Results are for 10000 Cells, function repeats 1000x
Swarm[CellType] Tests with 10000 Cell Record-Types: 0 ms
Object.keys(): 1111 ms
Object.values(): 2162 ms
Object.entries(): 2741 ms
Object.entries().forEach with key,value and index: 2797 ms
for...in: 1184 ms
for...in counted: 1316 ms
for...of .entries(): 2610 ms
for... over index of keys from Object.keys(): 1113 ms
Array Tests with 100000 entries: 0 ms
array for loop: 53 ms
array for...of: 618 ms
array forEach: 478 ms
array for...in: 5284 ms
array Object.entries(): 11103 ms
array map(): 1264 ms
Records vs Map Tests: 0 ms
obj[key] Lookup random 1000 of 1M: 14062 ms
map.get(key) Lookup random 1000 of 1M: 11271 ms
map for...of - Loop 10000: 150 ms
Esbuild TS -> JS. Results are for 10000 Cells, function repeats 1000x
Swarm[CellType] Tests with 10000 Cell Record-Types:
Object.keys(): 408 ms
Object.values(): 117 ms
Object.entries(): 380 ms
Object.entries().forEach with key,value and index: 380 ms
for...in: 335 ms
for...in counted: 557 ms
for...of .entries(): 377 ms
for... over index of keys from Object.keys(): 394 ms
Array Tests with 100000 entries:
array for loop: 78 ms
array for...of: 672 ms
array forEach: 544 ms
array for...in: 9059 ms
array Object.entries(): 8157 ms
array map(): 1121 ms
Records vs Map Tests:
obj[key] Lookup random 1000 of 1M: 58130 ms
map.get(key) Lookup random 1000 of 1M: 1202 ms
map for...of - Loop 10000: 1042 ms
TLDR: Use Map<T,T> and Array<T> only lol
Data Structure | Recommended |
---|---|
Array of objects (T[]) | forEach, for-of, map |
Object as dictionary (Record |
Object.entries(obj) + for-of |
Object that needs array-like behavior | Add [Symbol.iterator] |
Fastest method for performance-sensitive tasks | Classic for loop |
keyed Record<string, T> | Fastest: for | Construct | Example | How | Index | Key | Value | Use Case |
---|---|---|---|---|---|---|
for in | for (const key in obj) | enumerate properties, numeric ordered, string insertion order, symbol keys are ignored | ❌ | ✅ | ✅ (obj[key]) | Fast ⚠️ includes prototype properties |
for of | for (const value of obj) | expects iterator to enumerate property-values | ❌ | ❌ | ✅ | Fast ⚠️ includes prototype properties |
.forEach Keys | Object.keys(obj).forEach(key => { ... }) | These all will return you another object which maps index, keys and values. | ↪️ (derived) | ✅ | ✅ (obj[key]) | Safe, avoids prototype properties |
.forEach Values | Object.values(obj).forEach(value => { ... }) | ↪️ (derived) | ❌ | ✅ | Safe, values only | |
.forEach Keys+Values+Indizes | Object.entries(obj).forEach(([key, value], index) => { ... }) | ↪️ (derived) | ✅ | ✅ | Safe | |
for of .entries | for (const [key, value] of Object.entries(obj)) | ❌ | ✅ | ✅ | 🚫 in some styles "Clean and readable" | |
indexed Array<T> | Fastest: for | |||||
Construct | Example | how | Index | Key | Value | Use Case |
for | for(let i = 0; i < arr.length; i++) | ✅ | ❌ | ✅ Yes (arr[i]) | Fastest, manual index handling | for of | for (const value of arr) | uses iterator of object | ❌ | ❌ | ✅ | Readable, avoids index | for in | for (const index of arr) | this will turn your index into strings | ✅ | ❌ | ✅ (arr[parseFloat(index)]) | Don't use that, Slow as f... (10%) |
.forEach | arr.forEach((value, index) => { ... }) | ✅ | ❌ | ✅ | Callback-based, readable, fast | |
.map, .reduce, .filter, etc. | arr.map(value => value * 2) | higher-order callbacks | ❌ | ❌ | ✅ (Returns new array) | surprisingly slow 65% |
.entries | arr.entries() + for (const [index, value] of arr.entries()) | ✅ | ❌ | ✅ | Best for index & value |