Vast performance improvements;#197
Conversation
| if (!(item = args[i])) | ||
| continue; | ||
| type = typeof item; | ||
| if (type === "string" || type === "number") { |
There was a problem hiding this comment.
The only thing which i dont get - why do we have to pass the numbers here?
Due to W3C's TR - they're NOT valid class names.
There was a problem hiding this comment.
This handles use cases like this:
classNames('foo', 23); // => 'foo 23'There was a problem hiding this comment.
I know what it does, 23 is invalid classname (as any number leaded string will be), that's I wonder why this lib passes it to result.
I mean I don't know the practical usage of the numbers in classnames, does anyone do like that?
…ore it's results -> squeezed a bit more perf;
…ower than regular one;
Leads to way better performance in FF and legacy browsers;
Leads to way better performance in FF and legacy browsers;
|
Those are some big improvements to the benchmarks @xobotyi, awesome! I'm reviewing this now and can't see any issues with merging your changes. Out of curiosity, would you mind annotating the PR (adding your own review is a good way to do this) with some explanations of why the changes you made improved the performance? Thanks! |
| var hasOwn = {}.hasOwnProperty; | ||
|
|
||
| function classNames () { | ||
| var classes = []; |
There was a problem hiding this comment.
@JedWatson The main idea here is to go away from arrays.
Due to the iterative process we can replace array accumulator with simple string.
It gives us two benefits:
- String concatenation is faster than array push.
- Because we are generating the result on the fly - there is no need of
classes.join(' ')which in fact caused second array iteration (first was over the function arguments).
Secondary: i've moved variables declaration out of the loops (it gives pretty big impact).
Plus, IE's optimizer works better if to make typeof separately without storing it's result into a variable.
Bind version works pretty much the same.
There was a problem hiding this comment.
Could you quantify the performance differences of everything other than the String concatenation change?
There has been several discussions about the two approaches within this project [and others].
The results appear to vary on every build of V8.
There was a problem hiding this comment.
@dcousens it impacts not only the V8 based browsers, FF has performance improvements too.
But as i mentioned above - logically, strings based approach has a benefit of one less array iteration (at the end when joining happens).
In general, i suppose all other changes will give around 2-5% performance improvenet.
| _parse(classSet, arguments[n]); | ||
| } | ||
|
|
||
| function _classNames () { |
There was a problem hiding this comment.
@JedWatson almost the same optimisation reason here (but the real showstopper here is strings split, with which we can do nothing).
But here we had even worse situation in terms of iteration:
- Lines 76-80 are literally useless. It creates new, real array of
argumentselemnts and then araing iterates over newborn array at lines 87-91; It would've made sence if you passes that array further to other functions but not here. - And then, again, simply aviding the array accumulator while building the final string;
- Futrthermore i've simply merged parsers into single function, that gives us economy on function calls;
|
Done! |
|
The original implementation did something similar. |
|
@newyork-anthonyng looks kinda weird to me, to speculate readability of code you'll never read with obvious performance tradeoff in main lib usecase👺 Just saying🙊 |
Single expression statements replaced with one-liners;
|
Will inspect this soon 👍 |
|
|
||
| if (typeof item === 'string' || typeof item === 'number') { | ||
| str && (str += ' '); | ||
| (str += item); |
There was a problem hiding this comment.
Since we're chasing milliseconds, won't it be faster to just assign once?
str = (str ? ' ' : '') + item;
There was a problem hiding this comment.
Practically, for v8 my aproach is faster, for other browsers they're almost equal;
I suppose it is caused by prediction branching.
In your way you have 2 concats all the time, in mine - 1 guaranteed and one optional;
There was a problem hiding this comment.
Yes, but my gut feeling was that the extra concat done on item 0 is worth avoiding the extra variable mutations for items 1+.
There was a problem hiding this comment.
@krawaller benchmarks result:
mine: local#strings x 14,682,684 ops/sec ±1.21% (89 runs sampled)
yours: local#strings x 14,028,615 ops/sec ±1.01% (89 runs sampled)
It is obviously a very small difference but if it can be done - why not =)
There was a problem hiding this comment.
Yeah, I guess we've left the real world far behind. :)
Still, very interesting! I really thought variable mutation would be more expensive, but yours win out. Predictive branching for the win, I guess!
There was a problem hiding this comment.
@krawaller tbh first time i've seen the results i've been wondered too =)
First iteration code been written as yours %)
|
It very likely won't make a measurable difference, but I'm curious if string constants would be an improvement. var EMPTY = ''
var SPACE = ' ' // (except regex already called this)
var OBJECT = 'object'
var FUNCTION = 'function'
// ...etcIt might well be that variables perform worse than literals (when not using |
|
@krawaller there are no perf improvements if to store them in variables, even more - it can slow down due to disabling the typecheck engine optimisation on rare browsers, for them typeof a === 'string'will be faster than var STR = 'string';
typeof a === STR;or even var type = typeof a;
type === 'string' |
|
Ah, roger that, @xobotyi! |
|
Are there any plans to merge this? |
|
@JedWatson , ping again. Pls help to review and merge. Thank. |
|
cc: @JedWatson any updates on this, please? |
|
Any updates? Is there anything blocking the perf improvement? |
|
@pegak nothing, imo. |
Reworked all implementations (regular, dedupe, bind).
No breaking changes.
Benchmarks: