Styled-components: Performance with 2.0.0

Created on 14 Mar 2017  Â·  48Comments  Â·  Source: styled-components/styled-components

Disclaimer

This might be something that you guys are already aware of OR it might just be insignificant.

Version

2.0.0-7 and 1.4.4

Reproduction

I ran the tests in styled-components/benchmark and 2.0.0-7 was significantly slower.

Processor specs:
2.6 GHz Intel Core i5
8 GB 1600 MHz DDR3

Results:

Results for browser (could not get native to work for me)

test                               1.4.4          2.0.0-7        increase
---------------------------------  -------------  -------------  ---------
simple component                   0.085±0.011ms  0.186±0.046ms  2.19x
prop changes                       0.216±0.023ms  0.380±0.075ms  1.76x
prop shorthands                    0.099±0.027ms  0.186±0.066ms  1.88x
prop shorthands with prop changes  0.261±0.071ms  0.359±0.099ms  1.37x

Let me know if this is useful

enhancement

Most helpful comment

Just merged #619 and released v2.0.0-11. Results on my machine without pre-processing with the Babel plugin:


Detailed results

v1.4.4

simple component                   0.120±0.046ms 
prop changes                       0.275±0.040ms
prop shorthands                    0.096±0.017ms
prop shorthands with prop changes  0.260±0.050ms

v2.0.0-10

simple component                   0.212±0.073ms
prop changes                       0.511±0.098ms
prop shorthands                    0.260±0.088ms
prop shorthands with prop changes  0.678±0.321ms

v2.0.0-11

simple component                   0.182±0.063ms
prop changes                       0.371±0.076ms
prop shorthands                    0.188±0.064ms
prop shorthands with prop changes  0.422±0.100ms

Summary

v2.0.0-11 is much better, thanks @philpl!

                                   v1.4.4         v2.0.0-10      v2.0.0-11      v2.0.0-11 (preprocessed)
---------------------------------  -------------  -------------  -------------  -------------
simple component                   0.120±0.046ms  0.212±0.073ms  0.182±0.063ms  0.128±0.049ms
prop changes                       0.275±0.040ms  0.511±0.098ms  0.371±0.076ms  0.287±0.039ms
prop shorthands                    0.096±0.017ms  0.260±0.088ms  0.188±0.064ms  0.142±0.030ms
prop shorthands with prop changes  0.260±0.050ms  0.678±0.321ms  0.422±0.100ms  0.291±0.042ms

All 48 comments

Interesting! I know that the parser switched from postCSS to stylis—maybe that could be it?

Possibly, how can i debug that?

I doubt it is stylis, isolating just the parser it should perform better, will try to compare how postCSS handles this benchmark(see console) to say by how much.

Did anything else major change between the two versions?

Just the attrs constructor and the component selector. The component selector shouldn't be the bottleneck.

benchmark with post-css, stylis, css-tree and stylus rendering the same styles. stylis should improve performance in terms of the parser.

Strange. Were any other libraries updated, like glamour?

Does 1.4.4 use post-css asynchronously?

ran the bench, the third one is without any parser, replacing stylis with a noop function that just returns the string passed to it.

Results for browser v1
                                   styled         cssta        
---------------------------------  -------------  -------------
simple component                   0.124±0.036ms  0.020±0.006ms
prop changes                       0.336±0.085ms  0.140±0.084ms
prop shorthands                    0.198±0.112ms  0.024±0.009ms
prop shorthands with prop changes  0.351±0.084ms  0.079±0.015ms


Results for browser v2
                                   styled         cssta        
---------------------------------  -------------  -------------
simple component                   0.164±0.041ms  0.013±0.002ms
prop changes                       0.359±0.058ms  0.062±0.005ms
prop shorthands                    0.180±0.050ms  0.014±0.003ms
prop shorthands with prop changes  0.422±0.046ms  0.076±0.022ms


Results for browser v2(with stylis replaced with noop that just returns its input)
                                   styled         cssta        
---------------------------------  -------------  -------------
simple component                   0.153±0.031ms  0.014±0.003ms
prop changes                       0.350±0.047ms  0.061±0.008ms
prop shorthands                    0.143±0.027ms  0.014±0.003ms
prop shorthands with prop changes  0.388±0.079ms  0.069±0.004ms

Edit: ...actually i don't think webpack bundled v2 when i updated package.json dependencies so they might all be v1, how would one go about bundling v2?

Thanks for that post! If you npm installed v2, it would be fine.

Something is definitely wrong if without a parsing step it takes 10x as long as Cssta—I would expect them to be on par.

Would be great if somebody wanted to dig into this. I'd recommend doing what thysultan did and take the parser out completely. Without this step, styled-components should be able to match the performance of Cssta.

@necolas pointed me towards this on Twitter (thanks!), somethings definitely wrong somewhere. Unsure where this huge performance hit comes from, I'd appreciate somebody digging in!

@jacobp100 @thysultan I think I quickly ran these benchmarks with the preprocessor enabled and it was still more than v1, which means that it can't be stylis.

I can run the numbers again in a few minutes 😉

Without preprocessing:

                                   styled         cssta
---------------------------------  -------------  -------------
simple component                   0.119±0.025ms  0.009±0.002ms
prop changes                       0.276±0.035ms  0.042±0.009ms
prop shorthands                    0.144±0.035ms  0.009±0.002ms
prop shorthands with prop changes  0.333±0.091ms  0.054±0.007ms

With preprocessing:

                                   styled         cssta
---------------------------------  -------------  -------------
simple component                   0.101±0.018ms  0.009±0.002ms
prop changes                       0.200±0.037ms  0.044±0.007ms
prop shorthands                    0.098±0.019ms  0.009±0.003ms
prop shorthands with prop changes  0.206±0.028ms  0.044±0.009ms

With 1.4.4:

                                   styled         cssta
---------------------------------  -------------  -------------
simple component                   0.078±0.012ms  0.011±0.001ms
prop changes                       0.178±0.025ms  0.042±0.003ms
prop shorthands                    0.073±0.012ms  0.010±0.002ms
prop shorthands with prop changes  0.184±0.027ms  0.042±0.007ms

There indeed seems to be sth odd going on here :wink:

Something's very wrong. Any ideas?

@mxstbr Got it with some profiling. (At least for the first benchmark) We're missing the CSS cache every time since we're recreating the component, and thus creating a new componentId. So we're reinjecting the css every time. The culprit is this really:

const hash = hashStr(this.componentId + flatCSS.join(''))

So I guess at least the first one is expected behaviour.

The zip of the profiles

So, once we extract the component creation to keep the styles constant, to get a more comparable result, due to the difference I mentioned above, we get a more reasonable results:

1.4.4

----------------  -------------
simple component  0.009±0.001ms
prop changes      0.090±0.004ms

Preprocessed v2

----------------  -------------
simple component  0.010±0.001ms
prop changes      0.048±0.002ms

The small performance boost comes from a small change that I've done, that puts v2 at a benefit. Since in the second test, we're changing the props constantly, the componentWillReceiveProps method is triggered often. It turns out it was missing a simple check, whether the theme had actually changed. I discovered that it would update the state and call the CSS generation again, every time the props change. So I changed it to this for the test:

      componentWillReceiveProps(nextProps: { theme?: Theme, [key: string]: any }) {
        if (nextProps.theme !== this.state.theme) {
          const { theme } = nextProps
          const generatedClassName = this.generateAndInjectStyles(nextProps.theme, nextProps)
          this.setState({ theme, generatedClassName })
        }
      }

So, overall: I don't think there's anything to be concerned about actually :smile: :+1:

Edit: Totally forgot this! So, since everything is cached nicely now, stylis (in a non-preprocessed test) of course doesn't add more time...

----------------  -------------
simple component  0.010±0.001ms
prop changes      0.049±0.002ms

There's also no need to revert this change const hash = hashStr(this.componentId + flatCSS.join('')), since it does make sense.

One last thing, component instantiation has gotten 2x slower. This is not a big concern, I'd say since it's A) still not slow and B) a fraction of the cost for most apps.

v1.4.4

component instantiation (no render)  0.008±0.001ms

v2

component instantiation (no render)  0.019±0.003ms

Reopening since I'd like to actually improve performance by fixing two things:

  • Unnecessary regeneration of styles due to componentWillReceiveProps
  • Unnecessary duplicated ComponentStyle instantiations

once we extract the component creation to keep the styles constant

This is certainly a benchmark we should add, but the current ones were specifically benchmarking the time to first render. I should have made the benchmark titles clearer!

One thing to point out is that these benchmarks are running at peak JS perfo, so what seems like a small amount in the results could be much more for the first few components.

Just merged #619 and released v2.0.0-11. Results on my machine without pre-processing with the Babel plugin:


Detailed results

v1.4.4

simple component                   0.120±0.046ms 
prop changes                       0.275±0.040ms
prop shorthands                    0.096±0.017ms
prop shorthands with prop changes  0.260±0.050ms

v2.0.0-10

simple component                   0.212±0.073ms
prop changes                       0.511±0.098ms
prop shorthands                    0.260±0.088ms
prop shorthands with prop changes  0.678±0.321ms

v2.0.0-11

simple component                   0.182±0.063ms
prop changes                       0.371±0.076ms
prop shorthands                    0.188±0.064ms
prop shorthands with prop changes  0.422±0.100ms

Summary

v2.0.0-11 is much better, thanks @philpl!

                                   v1.4.4         v2.0.0-10      v2.0.0-11      v2.0.0-11 (preprocessed)
---------------------------------  -------------  -------------  -------------  -------------
simple component                   0.120±0.046ms  0.212±0.073ms  0.182±0.063ms  0.128±0.049ms
prop changes                       0.275±0.040ms  0.511±0.098ms  0.371±0.076ms  0.287±0.039ms
prop shorthands                    0.096±0.017ms  0.260±0.088ms  0.188±0.064ms  0.142±0.030ms
prop shorthands with prop changes  0.260±0.050ms  0.678±0.321ms  0.422±0.100ms  0.291±0.042ms

@mxstbr I'm still going to keep this open, since the performance isn't even close to 1.4.4 (except with preprocessing, but that's cheating) 😦

Yep for sure. I wonder where the regression is.

Where can I read something about 2.0 and the work that's being done for it? A roadmap maybe? So far I only found specific issue topics.

I'd like to add that I have serious performance issues when changing theme runtime on my app, it takes about 2s (production/device) to re-render 2 screens with two listviews and around 50 items.
PS: Lot's of components access props.theme and some of them use withTheme HoC.
It would be interesting a benchmark of this case.

--
PS2: First render is fast, the theme change is slow
PS3: On v1 as well
PS4: React Native 0.43 (any version), iPhone 6S

FYI I tried updating my benchmarks to run >2.0.0-7 but the UI rendering broke - looks like CSS rules are being inserted in the wrong order.

@necolas Can you post a link to your benchmarks?

@mxstbr Oh it's native! We haven't started on optimising native yet, @necolas.

But the CSS rule order sounds concerning

Edit: I think unfortunately this might be a regression, that I've accidentally put in. We might want to build up more unit tests around rule ordering https://github.com/styled-components/styled-components/blob/v2/src/models/ComponentStyle.js#L27

/cc @mxstbr @geelen

It's not native either

This is still an ongoing issue, since there's clearly a slight performance regression, still. That being said, it seems like we can go ahead with an initial v2 release, because this isn't a major problem anymore.

I just reran some tests, just to keep us up-to-date


Detailed results

v1.4.5

simple component                   0.074±0.010ms
prop changes                       0.189±0.021ms
prop shorthands                    0.077±0.012ms
prop shorthands with prop changes  0.198±0.028ms

v2.0.0-15

simple component                   0.116±0.025ms
prop changes                       0.267±0.031ms
prop shorthands                    0.137±0.056ms
prop shorthands with prop changes  0.322±0.102ms

v2.0.0-15 (preprocessed)

simple component                   0.089±0.016ms
prop changes                       0.197±0.036ms
prop shorthands                    0.095±0.018ms
prop shorthands with prop changes  0.208±0.036ms

I was just curious :wink:

Summary

| Benchmark | v1.4.5 | v2.0.0-15 | v2.0.0-15 (preprocessed)
| --- | --- | --- | --- |
| simple component | 0.074±0.010ms | 0.116±0.025ms | 0.089±0.016ms |
| prop changes | 0.189±0.021ms | 0.267±0.031ms | 0.197±0.036ms |
| prop shorthands | 0.077±0.012ms | 0.137±0.056ms | 0.095±0.018ms |
| prop shorthands with prop changes | 0.198±0.028ms | 0.322±0.102ms | 0.208±0.036ms |

Changes

| Benchmark | v1.4.5 | v2.0.0-15 | v2.0.0-15 (preprocessed)
| --- | --- | --- | --- |
| simple component | 100% | 157% | 120% |
| prop changes | 100% | 141% | 104% |
| prop shorthands | 100% | 178% | 123% |
| prop shorthands with prop changes | 100% | 163% | 105% |

It seems that nothing has changed, which is neither good nor bad.

Hi @philpl, just wanted to make sure I understand these benchmarks; v2 does indeed currently have a performance regression then?

@jstejada a slight one, bit after we discussed the weights of different fsctors, we realised it came down to the added logic when constructing our chained API.

The problem with the benchmarks are that they don't take the biggest factor into account: The stylesheet injection.

But actually, @geelen is working on a fix for batching the injection, and with v3 we should be able to bring it on par with glamorous and emotion

great, thanks for the clarification @philpl !

Looks like I'm experiencing performance issues too. The interface I'm working on contains a few elements that can be toggled (selected). When I interact with these in Safari and Chrome on iOS for the first time, re-rendering sometimes takes over a second and everything freezes during this.

Here's how a timeline looks in Safari on iOS 10 / iPhone 6s when a simple _styled-components_ switch is toggled for the first time:

screen shot 2017-09-05 at 08 30 22

(the longest event is styles recalculated that follows styles invalidated)

When the same switch is toggled back or toggled on again, everything is smooth. This suggests that an issue might be with how the new <styles> are added. The delay takes place only for any new unique combinations of props that get passed to the styled components. I.e. if I have two switches with two icons, toggling both of them for the first time is slow. If the icon is the same (i.e. icon+toggled props are the same), the delay is experienced only once. This freezing is not noticeable in the desktop browsers.

Should this go to a separate issue? Who else is seeing the same thing?

__PS:__ running v2.1.2

Try running against the current master branch version (you should be able to install @next maybe?) we've done some big improvements on un- and remounting speed thanks to @schwers!

Thanks for the hint @mxstbr. npm i styled-components@next installs [email protected]. I just tried [email protected] (the latest known release in this repo dated 9 days ago) and haven't noticed any improvements in the production version of my create-react-app instance.

Looking forward for a new release! Let know if you need any help with testing.

Hey guys! When about are you planning to cut a new release? Just curious to see if there any performance improvements after a contribution from @schwers. Trying that on the weekend would be awesome!

Hi guys, I thought it might be useful to put my tests for release 2.1.3-0 which has big performance enhancement by comparing it with react-fela and styletron, almost the same or faster in prod env. I'm new to css-in-js and just getting my hands wet comparing some of the interesting frameworks.
Hope to see more info about the enhancements done in this release that gave that performance hit.

ping @mxstbr :–)

Publishing v2.2.0 with the performance improvements!

I just upgraded styled-components to 2.2.0 and released a new version of the app, but the performance issue I mentioned in the comment above remained. 🤔

Just curious: who else has it? Check out the first unique renders of togglable buttons and other styled elements that have "states" based on props on iOS! 👀

@kachkaev Oh shoot! Forgot to reply.

Most time seems to be actually spent in the click event dispatcher (118ms) vs style recalc (11ms). I'm not sure why the composite is at 931ms, doesn't seem quite right. Have you checked whether it's the CSS itself that is really expensive to recomposite? It doesn't seem to be on the JS side, so out of our control, pretty much.

Basically, the stack is in its form there not that useful, since I'm not familiar with Safari's tool that much. If you can reproduce it separately, I can have a look with some other tools, but it doesn't look to be a css injection bottle neck, and neither a js bottle neck.

Edit: generally I think it's a good idea to create a separate issue. This is not an umbrella issue for all performance problems as they can have unrelated / different sources. This is more to track overall progress :wink:

I guess I figured out the performance issue that I reported in the comment above. It is to do with how heavy SVGs are cached and rendered in iOS browsers after a new styled-components rule appends to the page.

Details: https://github.com/kachkaev/styled-components-with-heavy-svgs-mwe

Not a bug of style-components as such, so no need to create a new issue.

The delay in rendering could have been avoided only if styled-components did not generate new CSS rules as the app runs. However, this is not possible by design, because there is no way to predict how the new props will affect the output styles.

@kachkaev this is actually improved with #1069 by @schwers, which I'll release rn as v2.2.1. What that does is cache static rules, meaning if you have a styled component with 0 function interpolations we'll never reinject or reparse that CSS! :tada:

Thanks for releasing v2.2.1 @mxstbr! A quick update on my heavy-SVGs case: an upgrade from v2.2.0 did not help :smiley:

I believe the problem with rasterizing SVGs in iOS Safari and Chrome is on a deeper level than styled-components can reach. Looks like any change to the set of global css rules invalidates all the SVGs and they require a redraw. This even happens when a styled-component that does not have any SVGs in it gets a new combination of props (i.e. a new unique class name). There's probably nothing to fix in this library and the only option is to avoid heavy SVGs in the interface.

@kachkaev Can you open a new issue for this use-case? It seems that we'll need a separate place to play around with and discuss it. I don't believe it's an issue on our end but I'd like to take a look regardless to be sure :wink:

All the low hanging fruit mentioned in this thread have been done, so I'm going to close this issue to clean up our issue tracker again.

Further work is happening in #1049, #1117, #1208, and some others in case you want to follow along further, with the likely biggest improvement overall happening in #1208.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

probablyup picture probablyup  Â·  68Comments

Nemsae picture Nemsae  Â·  65Comments

rtymchyk picture rtymchyk  Â·  42Comments

brad-decker picture brad-decker  Â·  66Comments

tal picture tal  Â·  100Comments