donejs / done-component

A plugin for creating <can-component>s
https://www.npmjs.com/package/done-component
MIT License
8 stars 2 forks source link

Performance penalty to using .components #26

Closed phillipskevin closed 7 years ago

phillipskevin commented 7 years ago

I ran some tests in a freshly made DoneJS 1.0 app (built / minified) by creating nested .component files and then nested files using modlets. Here is what the 10 .component test looked like:

image

I benchmarked this using 400 concurrent connections, 12 threads, and ran the test for 1 minute. Here is an example test run:

$ wrk -t12 -c400 -d1m http://localhost:8080
Running 1m test @ http://localhost:8080
  12 threads and 400 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.43s   250.94ms   2.00s    84.36%
    Req/Sec    25.67     20.04   121.00     72.41%
  15401 requests in 1.00m, 11.53MB read
  Socket errors: connect 0, read 444, write 0, timeout 813
Requests/sec:    256.31
Transfer/sec:    196.48KB

It seems there is a performance penalty to using .component files vs using modlets. Here are the results I have so far:

Num Components Requests / Second with modlets Requests / Second with .component
1 256.31 225.94
5 168.14 111.28
10 110.06 64.21
matthewp commented 7 years ago

Do you have a script that will produce this? I would want to look at the profiler.

phillipskevin commented 7 years ago

I pushed the code I used here: https://github.com/phillipskevin/donejs-modlet-comparison.

matthewp commented 7 years ago

I ran the test 3 times with 10 components. Here are the results:

Run Requests / Second with modlets Requests / Second with .component
1 134.74 81.38
2 136.06 85.46
3 133.60 84.05
matthewp commented 7 years ago

I have a theory on what might be the issue. Going to try a couple of things.

matthewp commented 7 years ago

So, my suspicion was that this is because of <can-import>. Any can-import, even the static variety, causes a call to System.import(name). This call is more expensive for plugins than for any other identifier (although it is expensive in both cases) because the plugin has to be normalized twice (once for each side of the bang).

What I did was go inside of node_modules/can-view-import/can-view-import.js and add a return to the top of the tag callback. This prevents it from importing the module each time.

This will work in this app because there are only static imports. If you have dynamic imports you'd have to account for that, (maybe add a static attribute or something so you can tell the difference). Here are the results:

Run Requests / Second with modlets Requests / Second with .component
1 227.98 177.75
2 225.25 176.75
3 226.89 174.92

So, as I suspected, the import calls are slowing down rendering. What's interesting here is that while the .component imports were helped (almost double the number of requests), the modlets are still faster.

So I wonder if it's possible that there is something else going on here as well. Note that the gap is about the same, 50ms or so faster for modlets.

matthewp commented 7 years ago

I'm going to measure just the import part and see how slow that is.

matthewp commented 7 years ago

Looking more into it, it seems that a single import is very fast, maybe 1 or 2ms. However when multiple imports are taking place at once, in this case 10, it significantly slows down each one, to 10-20ms. I'm looking into why.

matthewp commented 7 years ago

One thing we could possibly do is hook normalize and cache the value after a module has been imported. This would prevent it from having to do the expensive normalize algorithm every time. I'm going to give this a shot.

phillipskevin commented 7 years ago

I'm also looking into the differences in how a .component's view and a stache file get compiled and rendered.

matthewp commented 7 years ago

I have a branch in steal that does cache of normalize once an app is fully loaded. This prevents the extra normalization costs on requests.

This can be used with:

{
  "devDependencies": {
    "done-serve": "supercache",
    "steal": "supercache"
  }
}

Here are the results:

Run Requests / Second with modlets Requests / Second with .component
1 208.39 211.79
2 209.06 211.50
3 208.39 212.06

Conclusion

Using the supercache the .component modules can serve 2.5x as many requests per second. And are about the same as an app using modlets. This leads me to believe that normalization is the reason for this performance issue.

I'm going to think some more about whether the method for which this was implemented is the right way, or if this is better implemented as something like a done-ssr feature.

matthewp commented 7 years ago

@phillipskevin I'm going to move this to done-ssr as I think it's a general SSR performance issue and done-component doesn't have anything specific about it that's causing it.

matthewp commented 7 years ago

Issue moved to donejs/done-ssr #223 via ZenHub