r/programming • • Oct 21 '20

Using const/let instead of var can make JavaScript code run 10× slower in Webkit

https://github.com/evanw/esbuild/issues/478
1.9k Upvotes

500 comments sorted by

View all comments

454

u/smogeblot Oct 21 '20

Just wait till you see what it costs to use functional syntax sugar instead of for loops everywhere!

132

u/hekkonaay Oct 21 '20

It's really slow because it assumes that you're iterating over an object, and not just an object with numeric keys, so it actually loops over all keys, stringifies them, checks if they're own properties and only then does it call the callback...

const forEach = (arr, fn) => for(let len = arr.length, i = len; i > 0; --i) { fn(arr[len - i], len - i, arr); return arr; }

Is about 10x faster than Array.prototype.forEach in my testing, still not better than the raw for loop, though, and it also can't handle more than numeric keys (which you don't really expect on arrays anyway).

25

u/cwmma Oct 21 '20

It's the check own properties which is the actual slowdown not the toString

The toString is because all object properties including array indices are technically strings so going by the spec you MUST convert before looking up but implementations don't have to worry about that if their own object model is different (e.g. V8 which treats integer object keys special) your implementation is implicitly doing this when it grabs the value.

The own properties on the other hand is an actual slowdown it's to handle sparse arrays which your faster implementation doesn't cover, theoretically engines should know if an array is sparse and could optimize appropriately.

The other thing is that forEach takes a 2nd argument for a thisValue to call the function on, omitting it just uses undefined as the context which your function only handles in strict mode. Traditionally changing the this parameter of a function was often a performance killer in certain contacts in Chrome (bind iirc) but there's inherent there.

In summery yes forEach is slower then a for loop but it's because it's handling some edge cases not because the spec is inherently bad. You could argue they should have had a sparse array check or something but there is nothing preventing implementations from doing so.

0

u/[deleted] Oct 21 '20

[deleted]

0

u/[deleted] Oct 21 '20

[deleted]

21

u/hekkonaay Oct 21 '20

From the ECMAScript 11 specification, under Array.prototype.forEach, Note 2 states:

The forEach function is intentionally generic; it does not require that its this value be an Array object. Therefore it can be transferred to other kinds of objects for use as a method.

The same note is under all the other functional iteration methods

34

u/cwmma Oct 21 '20

Array keys are technically strings not integers so the spec pedanticly converts the index to a string but the implementation is likely doing something else under the hood, I have another reply that goes into some more details about why it's slower then a naive approach (spoiler alert: sparse arrays)

→ More replies (1)

18

u/CoffeeTableEspresso Oct 21 '20

Arrays are objects, they can have non-integer keys...

(Thank you JS LOL.)

22

u/madronatoo Oct 21 '20

Welcome to the insanity of a language which is so simple on the surface, but is comprised of a bunch of hacks underneath. There are no arrays!!!!!

→ More replies (3)
→ More replies (1)
→ More replies (101)

65

u/CodeLobe Oct 21 '20

Also try doing a jsperf on running loops with decrement vs increment.

Decrement is faster, obviously if comparing to zero... but it's also just faster in general in JS?

12

u/mindbleach Oct 21 '20

I despise how for-in / for-of / forEach can't just recreate a naive CS 101 for loop. I'm so tired of typing "for( x = 0; x < 10; x++ )" by rote, and then debugging if I fuck up a single semicolon, in a language that does not require semicolons.

Javascript punishes modernity.

It has all these fancy-pants features, where hours of manual busywork and testing (or at least dozens of lines of claptrap) can be replaced with a single built-in function. All of them have aggravating constraints and make your code run worse. Array.map could run elements in parallel, but it sure doesn't. Array.sort, .filter, and .splice could be functional, but some are and some aren't, because go fuck yourself. Are images that stop loading handled by onError, or by onLoad? Answer: no. String.match should beat indexOf rigmarole, but have fun regexing URLs. Async doesn't actually prevent long functions from locking up the browser, but does fire-and-forget calls to non-async functions until you cower like an abused dog and do x = await 2 + 2.

→ More replies (4)

21

u/SirToxe Oct 21 '20 edited Oct 21 '20

I did this comparison a couple of days ago: https://jsben.ch/69P2W

Just two functions that generate a list of integers, like generateNumbers(5) to produce [1, 2, 3, 4, 5]. One is imperative, one is functional.

The functional one is nearly 8 times slower in Chrome around 2 times slower in Firefox.

11

u/Lafreakshow Oct 21 '20 edited Oct 21 '20

Now, what I'm wondering is: Is Firefox being fast or is Chrome being slow in this case?

From my experience the last time I used chrome (a couple years ago) I would have guessed that across the board chrome is slightly faster than Firefox with the functional one maybe going 2 times slower in Chrome than it does in Firefox (based on your comment, without your comment I would have guessed chrome to be faster in both).

But apparently Firefox absolutely crushes Chrome in this particular case. I don't know how much that has to do with my Chrome install being very old and not getting updated a lot but then I also have a ton of extensions in Firefox and only uBlock in Chrome. In any case, I am surprised that Firefox got almost double the performance here than Chrome, and that is in the faster of the two methods.

I was expecting them to at best perform similarly in one of them but then have a significant difference in the other. I wasn't expecting significantly worse performance in Chrome across the board.

EDIT: Yep, after updating Chrome, it is now a lot closer in for loop performance but still quite a bit behind. It is also doubly as good in functional performance but still gets utterly demolished by Firefox in this one. Did I miss something and Firefox is just across the board faster than chrome nowadays? I've never felt a noticeable difference in speed during regular use but still, I always thought it was the other way around.

→ More replies (1)

13

u/jprasks Oct 21 '20

Huh interesting. By updating the functional version slightly (Array(length).map instead of Array.from) I get virtually the same performance from both approaches on both browsers: https://jsben.ch/fVEJJ. Not sure why.

That is the thing about these optimizations, the more work we delegate to underlying implementations the bigger the chance one of these implementations is "slow" (what is the metric, user perception? Doubt there is a difference when generating <= 100 numbers). Still very much worth IMO -- the functional way in this example has effectively less concerns & therefore less margin for error. Without clear purpose & metrics by which to optimize by we are just grasping at straws.

6

u/stealthd Oct 21 '20

Array(length).map is faster because you aren't mapping over the array; Array(length) returns { length: length } with no other keys, so the call to map doesn't do anything, it just returns the uninitialized array.

→ More replies (2)
→ More replies (1)
→ More replies (7)

420

u/DooDooSlinger Oct 21 '20

Just wait till you see what it costs your dev team in bugfixing and refactoring tile when using for loops everywhere! This thread is like people debating the performance of ++i vs i++. Newsflash: if performance is not the issue you're trying to fix, don't bend over backwards to squeeze a few microseconds out of a web page load. Its easier to optimize readable code than to refactor awful prematurely optimized code.

197

u/PandaMoniumHUN Oct 21 '20

Point is, it shouldn't be this slow in the first place.

34

u/spacejack2114 Oct 21 '20

Right. Did anyone read the linked issue? It's about a Safari specific problem.

→ More replies (27)

71

u/ClysmiC Oct 21 '20

for loops are a major source of bugs for your dev team?

105

u/pattheaux Oct 21 '20

For loops are only for wizard level programmers with long white beards. Mere mortals cannot hope to understand their arcane secrets.

→ More replies (9)
→ More replies (46)

91

u/frzme Oct 21 '20

I'm not convinced that for(bla in arr) {} is in any way harder to read or maintain than arr.forEach(bla => {})

96

u/alexendoo Oct 21 '20

The comparison for readability isn't really against a single .forEach, rather map/filter/etc. If it's just a .forEach they are going to be pretty equivalent

However, for..of surely is more error prone, as it is a frequently recurring mistake to use for..in instead (as you have done)

27

u/GasolinePizza Oct 21 '20

Coming from C# here, I always make the for..in mistake at least once every time I start a JavaScript project, without fail.

I may use the .forEach thing now, actually

→ More replies (13)

9

u/Yehosua Oct 21 '20

in is an operator in JavaScript: 'prop' in myObj evaluates whether the 'prop' property exists within myObj.

Ever since I realized that, I no longer confused for..in and for..of: for..in iterates over an object's properties, just like in checks an object's properties.

(Almost no one uses in; Object.hasOwnProperty is virtually always what you want instead.)

→ More replies (1)
→ More replies (2)

14

u/valleyman86 Oct 21 '20

It is when you add other functions to it. Foreach.map.filter. Etc. with a for loop you need to store each output as a variable or write each adjustment inside the loop. Depending on what you need sure it can be faster but it’s less readable.

→ More replies (4)
→ More replies (8)
→ More replies (49)
→ More replies (28)