Skip to content

Vast performance improvements;#197

Closed
xobotyi wants to merge 8 commits into
JedWatson:masterfrom
xobotyi:perf-improvements
Closed

Vast performance improvements;#197
xobotyi wants to merge 8 commits into
JedWatson:masterfrom
xobotyi:perf-improvements

Conversation

@xobotyi

@xobotyi xobotyi commented Jul 2, 2019

Copy link
Copy Markdown
Contributor

Reworked all implementations (regular, dedupe, bind).
No breaking changes.

Benchmarks:

> node ./benchmarks/run

* local#strings x 15,466,941 ops/sec ±0.36% (92 runs sampled)
*   npm#strings x 3,646,035 ops/sec ±1.69% (88 runs sampled)
* local/dedupe#strings x 2,648,941 ops/sec ±0.19% (93 runs sampled)
*   npm/dedupe#strings x 1,470,214 ops/sec ±1.71% (94 runs sampled)

> Fastest is local#strings

* local#object x 17,545,632 ops/sec ±0.42% (93 runs sampled)
*   npm#object x 3,899,880 ops/sec ±1.32% (93 runs sampled)
* local/dedupe#object x 7,118,462 ops/sec ±0.26% (91 runs sampled)
*   npm/dedupe#object x 2,632,530 ops/sec ±0.16% (94 runs sampled)

> Fastest is local#object

* local#strings, object x 11,379,569 ops/sec ±0.44% (95 runs sampled)
*   npm#strings, object x 3,259,972 ops/sec ±0.40% (94 runs sampled)
* local/dedupe#strings, object x 2,525,953 ops/sec ±1.49% (93 runs sampled)
*   npm/dedupe#strings, object x 1,548,802 ops/sec ±0.14% (92 runs sampled)

> Fastest is local#strings, object

* local#mix x 7,226,283 ops/sec ±0.20% (95 runs sampled)
*   npm#mix x 2,521,931 ops/sec ±1.39% (93 runs sampled)
* local/dedupe#mix x 907,851 ops/sec ±0.39% (93 runs sampled)
*   npm/dedupe#mix x 667,852 ops/sec ±0.84% (95 runs sampled)

> Fastest is local#mix

* local#arrays x 3,571,391 ops/sec ±0.17% (94 runs sampled)
*   npm#arrays x 919,980 ops/sec ±0.22% (91 runs sampled)
* local/dedupe#arrays x 1,028,421 ops/sec ±1.41% (95 runs sampled)
*   npm/dedupe#arrays x 743,585 ops/sec ±0.17% (94 runs sampled)

> Fastest is local#arrays

Comment thread index.js Outdated
if (!(item = args[i]))
continue;
type = typeof item;
if (type === "string" || type === "number") {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This handles use cases like this:

classNames('foo', 23); // => 'foo 23'

@xobotyi xobotyi Jul 6, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@JedWatson

Copy link
Copy Markdown
Owner

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!

Comment thread index.js
var hasOwn = {}.hasOwnProperty;

function classNames () {
var classes = [];

@xobotyi xobotyi Jul 5, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

@dcousens dcousens Jul 30, 2019

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@xobotyi xobotyi Jul 31, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Comment thread dedupe.js
_parse(classSet, arguments[n]);
}

function _classNames () {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 arguments elemnts 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;

@xobotyi

xobotyi commented Jul 5, 2019

Copy link
Copy Markdown
Contributor Author

Done!
Glad i did help😇 (actually first time contributing to such a big, in terms of users, package🤪)

@newyork-anthonyng

Copy link
Copy Markdown

The original implementation did something similar.
This PR changed it to the current implementation with arrays. It seemed like readability was the main reason of switching over.

@xobotyi

xobotyi commented Jul 6, 2019

Copy link
Copy Markdown
Contributor Author

@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;
@dcousens dcousens self-assigned this Jul 23, 2019
@dcousens

Copy link
Copy Markdown
Collaborator

Will inspect this soon 👍

Comment thread index.js

if (typeof item === 'string' || typeof item === 'number') {
str && (str += ' ');
(str += item);

@krawaller krawaller Aug 27, 2019

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're chasing milliseconds, won't it be faster to just assign once?

str = (str ? ' ' : '') + item;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but my gut feeling was that the extra concat done on item 0 is worth avoiding the extra variable mutations for items 1+.

@xobotyi xobotyi Aug 27, 2019

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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 =)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@krawaller tbh first time i've seen the results i've been wondered too =)
First iteration code been written as yours %)

@krawaller

Copy link
Copy Markdown

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'
// ...etc

It might well be that variables perform worse than literals (when not using const) since they are mutable and literals aren't.

@xobotyi

xobotyi commented Aug 27, 2019

Copy link
Copy Markdown
Contributor Author

@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'

@krawaller

Copy link
Copy Markdown

Ah, roger that, @xobotyi!

@dcousens dcousens removed their assignment Sep 23, 2019
@robertmaier

Copy link
Copy Markdown

Are there any plans to merge this?

@zombieJ

zombieJ commented Feb 18, 2020

Copy link
Copy Markdown

@JedWatson , ping again. Pls help to review and merge. Thank.

ref: ant-design/ant-design#21440

@resynth1943

Copy link
Copy Markdown

cc: @JedWatson any updates on this, please?

@dcousens dcousens self-assigned this Apr 1, 2021
@BleedingDev

Copy link
Copy Markdown

Any updates? Is there anything blocking the perf improvement?

@xobotyi

xobotyi commented Mar 30, 2022

Copy link
Copy Markdown
Contributor Author

@pegak nothing, imo.
I can offer you a drop-in replacement https://github.com/xobotyi/cnbuilder

@JedWatson JedWatson deleted the branch JedWatson:master September 13, 2022 03:13
@JedWatson JedWatson closed this Sep 13, 2022
@dcousens dcousens removed their assignment Oct 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants