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

Functional components always get re-rendered (Discussion) #1358

Closed
realcarbonneau opened this issue Dec 25, 2017 · 13 comments
Closed

Functional components always get re-rendered (Discussion) #1358

realcarbonneau opened this issue Dec 25, 2017 · 13 comments
Milestone

Comments

@realcarbonneau
Copy link
Contributor

All functional components always get re-rendered, even the hidden ones. See the video below and note the number of recurrence in the console window.

re-render

This is by design in Vue.js (functional components are cheaper), and this might be optimized in the future, but it is worth considering, since it is not always benificial to use functional components.
vuejs/vue#4037
jorgebucaran/hyperapp#373

Quasar Framework v0.15

@realcarbonneau realcarbonneau changed the title Functional components always get re-rendered (Discussion) Functional components alway get re-rendered (Discussion) Dec 25, 2017
@realcarbonneau realcarbonneau changed the title Functional components alway get re-rendered (Discussion) Functional components always get re-rendered (Discussion) Dec 25, 2017
@rstoenescu
Copy link
Member

Hi,

Thanks for taking an interest in performance.

Did a quick test: created a page with only a QModal having a QModalLayout. From this empirical test it seems that having QModalLayout as a functional component actually has a better performance than a regular component:

Regular:
screen shot 2017-12-26 at 17 06 20

Functional:
screen shot 2017-12-26 at 17 07 03

So it seems that by having QModalLayout as regular then this component is rendered an additional one time more. The "// ---" comment was typed directly in the browser console after each step (page initial render, type comment, click on btn to show Modal, type comment, click "Close" btn).

This is QModalLayout as a regular component (as opposed to a functional as it is now):

export default {
  name: 'q-modal-layout',
  props: {
    headerStyle: [String, Object, Array],
    headerClass: [String, Object, Array],

    contentStyle: [String, Object, Array],
    contentClass: [String, Object, Array],

    footerStyle: [String, Object, Array],
    footerClass: [String, Object, Array]
  },
  render (h) {
    console.log('QModalLayout render')
    const child = []

    if (this.$slots.header || (__THEME__ !== 'ios' && this.$slots.navigation)) {
      child.push(h('div', {
        staticClass: 'q-layout-header',
        style: this.headerStyle,
        'class': this.headerClass
      }, [
        this.$slots.header,
        __THEME__ !== 'ios' ? this.$slots.navigation : null
      ]))
    }

    child.push(h('div', {
      staticClass: 'q-modal-layout-content col scroll',
      style: this.contentStyle,
      'class': this.contentClass
    }, [
      this.$slots.default
    ]))

    if (this.$slots.footer || (__THEME__ === 'ios' && this.$slots.navigation)) {
      child.push(h('div', {
        staticClass: 'q-layout-footer',
        style: this.footerStyle,
        'class': this.footerClass
      }, [
        this.$slots.footer,
        __THEME__ === 'ios' ? this.$slots.navigation : null
      ]))
    }

    return h('div', {
      staticClass: 'q-modal-layout column absolute-full'
    }, child)
  }
}

Can you please reopen with how you see things getting improved? Thanks again.

@rstoenescu
Copy link
Member

rstoenescu commented Dec 26, 2017

It may be that the regular components are more suited when they have "reactive" props, even though they are more expensive to render at first. I'm still unsure which way to go, regular or functional. I feel we're missing out a metric to establish which is actually better in this case.

@realcarbonneau
Copy link
Contributor Author

A functional component are faster but are always re-redered. Normal components are a bit heavier to setup, BUT are only re-rendered if their props change.

The doc is quite clear:

...we can mark components as functional, which means that they’re stateless (no data) and instanceless (no this context)."

I am quite worried to see that most of the components have been converted to functional components which will always be re-rendered. In addition, this bypasses many optimizations that Vvue has designed.

https://vuejs.org/v2/guide/render-function.html#Functional-Components

Also see the coments by Linus Borg (from Vue) at https://forum.vuejs.org/t/performance-for-large-numbers-of-components/13545/10

Functional components are, well, like pure functions: props go in as arguments, virtual dom nodes come out as the return value. These components don't actually have an instance, no local data or lifecycle hooks etc.

That makes them cheaper during initial rendering than normal components.

But they also have a downside: Everytime the parent of a functional component updates, the functional component is re-rendered with it (i'm only talking about virutal DOM here, btw.). The reason for this is mostly technical in nature, as Evan explained:

Normal components, however, are heavier during initial rendering, because in addition to generating their virtual DOM nodes, they also have to set up their instance.

But that is a "one-time" cost that you pay during initial render. Now after this, whenever the parent updates because it's data changed, the child components are not affected by that, except if that data change affects their props."

Also note that as of Vue 2.5.0+, functional components can also be created using templates, so there is no reason to complexify the code to make functional components that are basically template based.
<template functional> </template>

@rstoenescu
Copy link
Member

It's not hard converting all functional comps to regular comps. This is not an issue. I am however buffed by the fact that in my empirical test the number of QModalLayout's render times is +1 when it's regular, as opposed to when being functional. This seems to contradict Vue authors.

@realcarbonneau
Copy link
Contributor Author

I created a test page with a bunch of QModal+QModalLayout. I ran it under 0.14.7 (Normal components), and 0.15 where QModalLayout is a functional component. During initial loading (refresh), contrary to what we would expect, the 0.14.7 loaded fastest, v0.15.0 took 50% longer to load. However, changing the screen had the same performance in both cases. In both cases, alot of time seems wasted for hidden components regardless of their type, however, I could not find the cause yet.

Processing Time (ms) = Total - Idle
Loading v0.14.7: 3701ms - 1799.6ms = 1901.4ms
Loading v0.15.0: 5550ms - 2475.9ms = 3074.1ms

Update v0.14.7: 9442ms - 5714.6ms = 3727.4ms
Update v0.15.0: 10035ms - 6285.5ms = 3749.5ms

It seems that using functional components or normal components does not make much difference. Also, using a template or createElement()/h() does not make a difference either.

Usually, it is best to keep the code as simple as possible until something is specifically proven to be a performance problem, instead of optimizing and complexifying, just in c

Eventually, I can try to build 4 versions of the same component (Functional/Normal/Template/Programmed), and 4 test pages with the component repeated 100 times within a collapsible or something similar.

Performance test v0.14.7
performancev0_14

Performance test v0.15.0
performancev0_15

Screenshots test v0.14.7
performancev14_load
performancev14_refresh

Screenshots test v0.15.0
performancev15_load
performancev15_refresh

@realcarbonneau
Copy link
Contributor Author

I redid the tests with the regular component you provided, so everything is under v0.15.0. In both cases, everything is being re-rendered, this is strange, I don't know why yet, this would be where the biggest performance gain can be found. As for the difference between Functional and Regular components, they are almost the same, functional components loading a bit faster, and regular components faster for changes. Since most people will spend very little time loading and most of the time using the apps, the regular components might be better in most cases. It's also better to keep the code simpler and only complexify when really neccessary. I will try test more with different scenarios.

Processing Time (ms) = Total - Idle
Loading Regular: 5158ms - 1953.7ms = 3204.3ms
Loading Functional: 5077ms - 2055.6ms = 3021.4ms

Update Regular: 9804ms - 6070.2ms = 3733.8ms
Update Functional: 9884ms - 5915.1ms = 3968.9ms

Video Regular Component
performancev0_15-regular

Video Functional Component
performancev0_15-functional

Screenshots Regular Component
performancev0_15-regular-load
performancev0_15-regular-update

Screenshots Functional Component
performancev0_15-functional-load
performancev0_15-functional-update

@realcarbonneau
Copy link
Contributor Author

Important to note that loading times were 50% longer in v0.15, independent of Functional/Regular components in the test. I am not sure what is the cause for this...

@rstoenescu
Copy link
Member

Awesome work here! Thank you! Will change all functional components to regular, then we'll see where we get from there.

@rstoenescu
Copy link
Member

Pushed:

commit b46d7637407e3199f4276be68cda5b71756618ec (HEAD -> dev, origin/dev, origin/HEAD)
Author: Razvan Stoenescu <razvan.stoenescu@gmail.com>
Date:   Wed Dec 27 22:41:44 2017 +0200

    refactor: Functional components to regular components

Can you test again and see how we perform now? I'd like to see a comparison between v0.14 - v0.15 before commit above, v0.15 after commit above.

Thanks again for all the help!

@rstoenescu rstoenescu added this to the v0.15 milestone Dec 27, 2017
@realcarbonneau
Copy link
Contributor Author

I have been working on various testing code and analysing the differences between the types of components. I will report results back shortly.

@realcarbonneau
Copy link
Contributor Author

It took me a while to figure out because the Quasar components where not behaving the way Vue says they should. All functional components get re-rendered whenever there is a reactive change anywhere in the DOM and regular components only re-render when the reactive change is relevant to them.

However, because most of your parent components were functional, everything was being re-rendered all the time because if the parent functional component gets re-rendered, all of its children get re-rendered or worse, re-created for regular components. This is easy to test, put the functional version of your q-card surrounding any of your demo pages with regular components (eg button.vue), and add reactive variable anywhere on the page (even hidden and outside of the q-card), and update the field. This will cause all quasar components to re-render, including the regular quasar components.

I created an app to test the two types of components, created two different ways and these are the results:

Functional components can be up to 5 times faster to create and up to 2 times faster to update. BUT only if they do NO work and have NO regular child components! (see screen shot 1)

Regular components that do alot of work (or have many children) will take the same time to create as functional components, but can be many times faster than functional components depending on the work they do and the children underneath since they will only do work required by the applicable reactivity. (see screen shot 2)

And there is no real difference between using template or programming components, so it would seem that Template components would be a better choice to reduce complexity and increase maintainability where applicable.

The app is in a pull request (#1366) now so you and others can test also. I included it under “web-test -> Performance” in the demo app.

No Work and No Child Components
performance testing nowork

With Work and/or Child Components
performance testing pluswork

@realcarbonneau
Copy link
Contributor Author

realcarbonneau commented Jan 3, 2018

@rstoenescu any thoughts or questions on these results and the pull request? In summary:

  1. Any component that can have child components should be regular components.
  2. Some very simple inner-most components without state could benefit as functional, maybe like Qvideo or Qicon. But this would have to be tested and benchmarked (the app I made could be modified for that)
  3. No significant performance difference between template and programming based components, so templates are better because they are easier to understand and maintain in many cases.

@realcarbonneau
Copy link
Contributor Author

realcarbonneau commented Jan 23, 2018

Here are some more results. This compares two versions of the QIcon component for 10,000 instances. The QIcon is a relatively simple component without much code.

QIcon Regular Component
qicon regular component

QIcon Functional Component
qicon functional component

Percentage of processing time of regular over functional components (positive number is regular is slower)
Create 229%
Delete 85%
External Update -13%
Render Update 71%

Clearly, the initial loading is alot faster for the functional component vesion, and a change to a reactive prop (in this case, changing the icon) causing a re-render is also faster. However, the regular component is 13% faster for any external reactive update (eg, all changes outside of the icon component).

It's a tradeoff depending on the needs. If it is a relatively static page, or one that is reloaded often or if the icons changes often, then a functional component would be faster over the long term. If it is a dynamic app with alot of changes happening constantly on the page, and mostly external to the icon (eg many modern SPAs), then the regular component will take less resources over the long term.

The best of both worlds might be SSR and regular components, but this will require further tests.

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

No branches or pull requests

2 participants