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 1 Million Cells, 100000 Links
Swarm[CellType] Tests:
Object.keys(): 117.90ms
Object.values(): 219.10ms
Object.entries(): 265.00ms
Object.entries().forEach with key,value and index: 276.70ms
for...in: 114.70ms
Array Tests:
array for loop: 5.80ms
array for...of: 122.10ms
array forEach: 81.70ms
array for...in: 496.50ms
array Object.entries(): 1188.70ms <--- wtf, 200x times slower
array map(): 127.50ms <--- airbnb style will slow your page!!!
Records vs Map Tests:
obj[key] Lookup: 1544 ms
map.get(key) Lookup: 97 ms
Esbuild TS -> JS. Results are for 1 Million Cells, 100000 Links
Swarm[CellType] Tests:
Object.keys(): 41 ms
Object.values(): 12 ms
Object.entries(): 36 ms
Object.entries().forEach with key,value and index: 42 ms
for...in: 32 ms <-- for 1 million cells btw.
for...in counted: 36 ms
for...of .entries(): 46 ms
Array Tests:
array for loop: 7 ms <-- means you can easily loop 250000 links at 60fps
array for...of: 64 ms
array forEach: 54 ms
array for...in: 887 ms <--- why FF is slower usually, never use for ... in on Arrays!
array Object.entries(): 840 ms <--- wtf, 200x times slower
array map(): 111 ms
Records vs Map Tests:
obj[key] Lookup: 5531 ms
map.get(key) Lookup: 116 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 |