Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve importantify() performance #201

Merged
merged 7 commits into from
Mar 7, 2017
Merged

Conversation

lencioni
Copy link
Collaborator

@lencioni lencioni commented Mar 4, 2017

I was rolling through the code here looking for some opportunities to
speed it up and I noticed that there is a capture group in this regex
that isn't being used. By changing ( to (?: we can avoid capturing
this group while still being able to use the parens for grouping.

I was also curious about the performance characteristics of various replace
arguments, and using a string in this way seems to have the edge, at least in
Firefox (35% faster) and Safari (12% faster)--Chrome performance seems
pretty similar for all possibilities. I also find this version more
readable, so it seems like we may as well go with this option.

https://jsperf.com/replace-string-vs-function/

I don't think we actually need a capture group at all here and can
accomplish the same thing by just anchoring on the end of the string.
This reduces the runtime of this function in my benchmark from ~105ms to
~35ms.

@xymostech
Copy link
Contributor

This looks great! Thanks for looking in to this.

On my computer, the jsperf says that the hoisted function replacement is the fastest for both chrome and firefox:

image

image

What are you seeing?

@lencioni
Copy link
Collaborator Author

lencioni commented Mar 4, 2017

screen shot 2017-03-04 at 3 46 06 pm

screen shot 2017-03-04 at 3 47 27 pm

But then I noticed my Firefox was pretty old so I updated. Here's the latest:

screen shot 2017-03-04 at 3 50 23 pm

So maybe function is the way to go?

@lencioni
Copy link
Collaborator Author

lencioni commented Mar 5, 2017

On my phone (Pixel), function hoisted is the winner with function and string being slowest.

@lencioni
Copy link
Collaborator Author

lencioni commented Mar 5, 2017

I think we can also simplify the regex here. If we change the code to this:

const IMPORTANT_RE = /(?: !important)?;$/;

// Given a single style rule string like "a: b;", adds !important to generate
// "a: b !important;".
export const importantify = (string /* : string */) /* : string */ =>
    string.replace(IMPORTANT_RE, " !important;");

it drops the runtime of importantify from ~105ms to ~36ms.

@lencioni
Copy link
Collaborator Author

lencioni commented Mar 5, 2017

FYI, I'm trying out all of my optimization PRs at the same time, and together they seem to speed up css() from 1.5s to 1.1s (~27%).

@lencioni
Copy link
Collaborator Author

lencioni commented Mar 6, 2017

Copy link
Contributor

@xymostech xymostech left a comment

Choose a reason for hiding this comment

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

Wow! This looks like a big improvement! I guess our importantify wasn't used enough to require something so complex. Thanks!

One small style nit, and we can get this landed :)

src/util.js Outdated
export const importantify = (string /* : string */) /* : string */ => (
string.slice(-11) === ' !important'
? string
: `${string} !important`
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: we use 4 space indents

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed!

@lencioni lencioni force-pushed the important-re branch 2 times, most recently from 99fbfec to bd0a575 Compare March 6, 2017 23:31
Copy link
Contributor

@xymostech xymostech left a comment

Choose a reason for hiding this comment

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

This code looks great! I'd love to see a comment explaining what's going on in that check, though.

src/util.js Outdated
// Given a single style value string like the "b" from "a: b;", adds !important
// to generate "b !important".
export const importantify = (string /* : string */) /* : string */ => (
string[string.length - 10] === '!' && string.slice(-11) === ' !important'
Copy link
Contributor

Choose a reason for hiding this comment

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

hehe this is great! Could you maybe add a comment explaining why this check looks so funky? Also maybe wrap these checks in parens because I can never remember the precedence of ?:? :P

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

src/generate.js Outdated
@@ -286,8 +286,7 @@ export const generateCSSRuleset = (

const rules = prefixedRules.map(([key, value]) => {
const stringValue = stringifyValue(key, value);
const ret = `${kebabifyStyleName(key)}:${stringValue};`;
return useImportant === false ? ret : importantify(ret);
return `${kebabifyStyleName(key)}:${useImportant === false ? stringValue : importantify(stringValue)};`;
Copy link
Contributor

Choose a reason for hiding this comment

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

This line is a bit long. :( Maybe split it up into two?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done!

lencioni and others added 6 commits March 7, 2017 09:22
I was rolling through the code here looking for some opportunities to
speed it up and I noticed that there is a capture group in this regex
that isn't being used. By changing `(` to `(?:` we can avoid capturing
this group while still being able to use the parens for grouping.
I was curious about the performance characteristics of various options
here, and using a string in this way seems to have the edge, at least in
Firefox (35% faster) and Safari (12% faster)--Chrome performance seems
pretty similar for all possibilities. I also find this version more
readable, so it seems like we may as well go with this option.

https://jsperf.com/replace-string-vs-function/
I don't think we actually need a capture group at all here and can
accomplish the same thing by just anchoring on the end of the string.
This reduces the runtime of this function in my benchmark from ~105ms to
~35ms.
Since we only call this function from one place, and we already have the
CSS property name and value split out, we can further optimize this hot
path by only running importantify on the CSS property value. In my
benchmark, this change speeds up this map by ~22% (~128ms to ~100ms).
Now that this has been sufficiently simplified, we can easily remove the
regex entirely.
In our microbenchmark, adding this optimization performs a good amount
better. Since it is a simple optimization to add, it seems like we may
as well do it.

https://jsperf.com/slice-vs-bracket-string-check-with-imporant

I considered using bracket notation for the whole check, but I decided
that was too verbose. So instead, I decided to just check for one
character and follow up with a more thorough but concise check
afterward. This optimization favors the case where the style does not
already have `!important` on it, which is by and large likely to be the
general case.
@xymostech xymostech merged commit dbb3879 into Khan:master Mar 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants