Vue: 10x performance drop when migrating to vue 2 what am I doing wrong?

Created on 10 Apr 2017  路  10Comments  路  Source: vuejs/vue

Version

2.2.6

Reproduction link

https://github.com/andrewluetgers/vue2-perf-test

Steps to reproduce

clone, npm install, npm run dev
in the ui click 100x100, click show, click run

What is expected?

frame rate should be above 100

What is actually happening?

frame rate in teens


compare to
https://github.com/andrewluetgers/vue-perf-test
exact same repo except uses vue 1 instead performs as expected at 100 fps

The key bit is in assets/src/components/AlphaTable.vue

<template>
    <div id="alphaTable">
        <ul v-for="row in layout" track-by="$index">
            <li v-for="cell in row" track-by="$index" :style="{background: cell.active ? cell.color : '#444444'}">
                {{cell.letter}}
            </li>
        </ul>
    </div>
</template>

Is there some production mode switch that needs to be flipped or do I need to break the nested v-for loops into separate modules or something?

Would love to understand what is going on.

Most helpful comment

Ok, so there are a few things involved:

  1. Your repo is using an old Vue 1.x config, using an outdated version of vue-loader and aliasing Vue to a build hard-coded in dev mode... I'd suggest using vue-cli to scaffold a fresh project and move the source code over. This doesn't affect the perf too much though.

  2. The more fundamental difference is in the different render strategies and reactivity boundaries between Vue 1 and Vue 2.

    • In Vue 1, reactivity boundaries are per-binding - that is, every text or style binding tracks its own dependencies. This has higher memory consumption cost but the advantage is the reactive updates are extremely granular by default: when a single cell's active property is toggled, only that cell's style binding is re-evaluated and updated.

    • In Vue 2, reactivity boundaries are per-component. Therefore, the AlphaTable component has a single watcher that keeps track of the dependencies used by every cell in the table. Every time a single cell changes, the entire component is re-rendered, incurring extra dependency collection, render and diffing costs.

    This can be dealt with, however, by turning every cell into its own component. This way when a cell's state is updated, only the corresponding cell component is re-rendered. Making this change brings the FPS back to 110~120, as one would expect. The drawback is that this results in slower initial render due to the cost of creating backing component instances.

    In fact, this is also the reason that your MobX + React test has high FPS because it is also using a component for each cell. If you use plain elements for each cell, you'd also see a significant FPS drop, down to just ~6 FPS.

    It might seem that Vue 1's strategy is better in this particular case - but in practice, it is extremely rare for a single component to contain 10k plain elements. In general, we've found Vue 2's strategy to be a better tradeoff for the more common use cases.

All 10 comments

Please read the 2.0 docs. You are using the old track-by which has been replaced by key.

On a second look it's probably not related to track-by, I'll do some profiling to see what's going on...

look forward to see the answer

Ok, so there are a few things involved:

  1. Your repo is using an old Vue 1.x config, using an outdated version of vue-loader and aliasing Vue to a build hard-coded in dev mode... I'd suggest using vue-cli to scaffold a fresh project and move the source code over. This doesn't affect the perf too much though.

  2. The more fundamental difference is in the different render strategies and reactivity boundaries between Vue 1 and Vue 2.

    • In Vue 1, reactivity boundaries are per-binding - that is, every text or style binding tracks its own dependencies. This has higher memory consumption cost but the advantage is the reactive updates are extremely granular by default: when a single cell's active property is toggled, only that cell's style binding is re-evaluated and updated.

    • In Vue 2, reactivity boundaries are per-component. Therefore, the AlphaTable component has a single watcher that keeps track of the dependencies used by every cell in the table. Every time a single cell changes, the entire component is re-rendered, incurring extra dependency collection, render and diffing costs.

    This can be dealt with, however, by turning every cell into its own component. This way when a cell's state is updated, only the corresponding cell component is re-rendered. Making this change brings the FPS back to 110~120, as one would expect. The drawback is that this results in slower initial render due to the cost of creating backing component instances.

    In fact, this is also the reason that your MobX + React test has high FPS because it is also using a component for each cell. If you use plain elements for each cell, you'd also see a significant FPS drop, down to just ~6 FPS.

    It might seem that Vue 1's strategy is better in this particular case - but in practice, it is extremely rare for a single component to contain 10k plain elements. In general, we've found Vue 2's strategy to be a better tradeoff for the more common use cases.

@yyx990803 This makes a lot of sense! Thanks for the explanation, and yes, it is very common this days to have very small and granular components and having optimizations done on a per-component basis is just right.

@yyx990803 thanks for the detailed explanation, will update the project. As you point out this test is a very particular and not reflective of most apps, things like game engines, particle systems etc are what I'm thinking of. It is a reasonable tradeoff that was made for Vue 2. I was quite surprised by the performance of Vue 1 on this test. I see the decoupling of performance from component composition as a big win in the simplicity side of things. In my dream world there would be some way to opt back in to Vue 1 reactivity boundary but thats just me :-) THANKS!

@yyx990803 just curious - what tools are you using to profile Vue?

Dave

@davidm-public

probably chrome dev tools - performance

@yyx990803 @davidm-public it was tested with https://github.com/andrewluetgers/vue2-perf-test and https://github.com/andrewluetgers/vue-perf-test note the frame rate counter and yes chrome dev tools profile and flame graph are great tools

@andrewluetgers thanks

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cherry-geqi picture cherry-geqi  路  35Comments

asiFarran picture asiFarran  路  34Comments

ferry77 picture ferry77  路  67Comments

wenLiangcan picture wenLiangcan  路  39Comments

yyx990803 picture yyx990803  路  36Comments