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

Performance improvements #2171

Merged
merged 17 commits into from Aug 18, 2020
Merged

Performance improvements #2171

merged 17 commits into from Aug 18, 2020

Conversation

RobinMalfait
Copy link
Contributor

@RobinMalfait RobinMalfait commented Aug 16, 2020

Perf

Tailwind is currently not super fast. Especially now with the newly added functionality for the recursive @apply rules.

This PR is an attempt to optimise some low hanging fruit and some hot paths. Let's see how this goes... 🤞


The results are performed using the following config files inside the ./perf folder. Relevant files:

I am testing this locally on a 16" MacBook Pro, 2,3 GHz 8-Core Intel Core i9, 32GB of RAM, AMD Radeon Pro 5500M 4GB machine.

Before:

   ✅ Finished in 4.22 min
   📦 Size: 101.19MB
   💾 Saved to perf/output.css

After (so far):

   ✅ Finished in 1.08 min
   📦 Size: 101.17MB
   💾 Saved to perf/output.css

@RobinMalfait RobinMalfait changed the title add perf utils Performance improvements Aug 16, 2020
@LionsAd
Copy link

LionsAd commented Aug 16, 2020

Yeah - especially the new code path for @apply in Vue components can make use of some nice caching as it’s always the same.

@adamwathan
Copy link
Member

adamwathan commented Aug 16, 2020

I'm not totally sure how to cache it but would be good if we could. Vue runs Tailwind from scratch for each component so it's a whole new instance of everything.

If we're lucky maybe we take advantage of modules acting like singletons but we can't really rely on scoped runtime level caching, only global. Have to be very careful to avoid incorrect cache hits for example when the config object has changed we need to expire the cache, so probably need to derive a cache key from the config somehow.

There is no need in re-compiling tailwind or building expensive lookupt
tables when it turns out that we don't even need it.

We have a small overhead by walking the tree to check if `@apply`
exists. However this outweighs the slowness of re-generating tailwind +
expensive lookup tables.
We were re-creating the classNameParser inside the loop. Since that code
is all pretty pure we can hoist it so that we don't have to recreate
that parser all the time.
Currently we will walk the tree for every single rule to see if an
`@apply` exists somewhere in that tree. However we don't use the
`containsApply` anymore so this is a quick win!
We create a big lookup table so that we can lookup the nodes by its
utilityName. This is used inside the recursive `@apply` code.

This big lookup table will clone every single rule and put it in,
however we don't need to clone everything! We are only interested in the
rules that have been actually applied.

This way we make the cloning of the rule lazy and only when we use this
exact rule.

There is an additional performace "issue" though: When we read the same
rule multiple times, it will clone every time you read from that object.
We could add additional memoization stuff, but so far it doesn't seem to
be the bottleneck. Therefore I've added a perf todo just to leave a mark
when this becomes the bottleneck.
We alreayd know that we have an `@apply` otherwise we would not have
called that function in the first place. Moving to a `do {} while ()`
allows us to skip 1 call to `hasAtRule(css, 'apply')`. Which is nice
because that skips a possible full traversal.
We don't require this reversed map since we can already sort by the
index on the node directly. Therefore this can be dropped.
No need to re-create the selector parser in the loop for each selector.
We used to clone the full tree and then remove all the children, this
was a bit too slow so therefore we will now create a new tree based on
the old information.
Naming is hard so I took this name from the React hook 😎

Also use this useMemoy utility to make sure that the extractUtilityNames
is cached. There is no need to re-compute the utility names all the
time.
No need to re-create the selectorParser in every call.
Otherwise every time we read this value it will be re-cloned
Same idea, but prepend will internally reverse all nodes.
@adamwathan adamwathan changed the base branch from master to 1.1.3 August 18, 2020 12:23
@adamwathan adamwathan changed the base branch from 1.1.3 to master August 18, 2020 12:23
@adamwathan adamwathan merged commit 9f9065d into master Aug 18, 2020
@adamwathan adamwathan deleted the perf-improvements branch August 18, 2020 12:29
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

3 participants