vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.73k stars 33.68k forks source link

v-for performance issue #2000

Closed carbon-projects closed 8 years ago

carbon-projects commented 8 years ago

hi, I have a table with a 300 row *60 column, using V-for's rendering is very slow, how to improve efficiency?

zigomir commented 8 years ago

@petersn-github I think your description lacks details. It would help a lot if you tell a bit more and/or show an example through JSBin or similar tool so one can see what is going on in your case.

yyx990803 commented 8 years ago

This is somewhat inevitable, the cell count is simply too large. There's currently no simple way to make it magically fast.

There are two possible ways around this:

  1. Rethink your UI design. Do you really need 18,000 cells all rendered at the same time? Does it even make sense? How about pagination?
  2. Do you need interactivity on every single cell? If not, you can scrap v-for and write your custom directive that simply renders the table with innerHTML. Or maybe you shouldn't be using Vue to render it in the first place.
smolinari commented 8 years ago

I am also interested in listing large amounts of data for reports. However, no real interactivity is needed, except for regular linking. I would imagine this is possible without any kind of performance issues? What would be the best practice to include such a large set of data into a vue?

Scott

yyx990803 commented 8 years ago

@smolinari pagination is probably the most reasonable solution, both performance and UX wise. The real bottleneck is how many items are rendered on the page at the same time. Just limit it to a reasonable amount.

I might work on a "virtual scroll list" where it only renders items in the viewport in the future.

smolinari commented 8 years ago

Pagenation for a report isn't really good usability.

The reports we are looking at would be limited to a maximum of 2000 or maybe 2500 rows, but there could be up to 15 columns. A lot of data, yes, but any decent reporting system would need to show that amount. Maybe need to come up with a completely different way to show this data? Hmm...maybe some sort of infinite scrolling might be the answer?

Scott

yyx990803 commented 8 years ago

I don't think any web framework can handle that many cells with acceptable rendering performance (assuming it's reactive instead of static HTML). So yeah, you have to do some dirty work to implement infinite scroll or virtual scroll.

KennedyTedesco commented 8 years ago

@yyx990803 I'm having the same Issue with 200 records, it cost almost 6s to render. It would be great a way to render without data binding ... This make any sense?

yyx990803 commented 8 years ago

@KennedyTedesco 6s for 200 records doesn't sound right. I assume you have more nested v-for? What does the template look like?

KennedyTedesco commented 8 years ago

@yyx990803 Sorry, my mistake. For my happiness the problem wasn't on Vue.

PrimozRome commented 8 years ago

@yyx990803 I am experiencing the same issue with only 50 records but not sure is it my problem or Vue's. Described here http://forum.vuejs.org/topic/3888/long-render-time-v-for.

PierBover commented 7 years ago

I've hit this problem and if you really must show all these records at the same time the simplest solution is to use divs instead of a table.

Render time in my case was reduced from 2 seconds to 100ms (aprox) for about 5500 rows * 8 columns (44000 cells).

But like @yyx990803 said usually the best option is to question whether you should be really painting all those cells. The are very few cases where showing all the data is justified and these are edge cases which need to be handled with care.

kinanson commented 6 years ago

I also have this issue.I have large size data 5000 row that is key and value. I binding in the ul li that is ok. But I binding in the select option that is so slow. I think select binding large size data is common situation. can i find the vue have any way to support without databind? Below is example.

<input type="checkbox" v-model="isCheck" />
    <select v-model="name" v-if="isCheck">
      <option v-for="(value,key) in competitions" :key="key">
        {{value}}
      </option>
    </select>
    <hr>
    <input type="checkbox" v-model="isCheck1" />
    <ul v-if="isCheck1">
      <li v-for="(value,key) in competitions" :key="key">
        {{value}}
      </li>
    </ul>

1

MaherSoua commented 6 years ago

Hi @kinanson , you can use :value ( v-bind:value) instead of v-model, I test it with 1000 element and it is really fast.

kinanson commented 6 years ago

In fact that is not real.I try no binding on select,It is also slow,Maybe you can tray more then 5000 rows.

MaherSoua commented 6 years ago

@kinanson I try it with 5000 is slow but ok, but with 10000 was really slow, but I think it is a browser issues more than other things because on my environnement it is a little bit faster than the demo gif you gave. (Mac i7)

Akryum commented 6 years ago

Did you try https://github.com/Akryum/vue-virtual-scroller?

Soletiq commented 6 years ago

This is a huge problem that needs to be addressed somehow.

Im using laravel and we needed an infinite scrolling table. We only load about 50 rows per batch. Ive been using vue for the entire project. We were using the v-for table but quickly noticed it couldnt handle a large amount of rows without slowing down the entire page. Also, scrolling becomes laggy and sticky.

My workaround was to dynamically create the table HTML and insert using v-html. But this is a terrible workaround. The code is extremely messy and hard to work with. I absolutely love using vueJS but this is a massive setback.

Do you need interactivity on every single cell? If not, you can scrap v-for and write your custom directive that simply renders the table with innerHTML

^ @yyx990803 Why hasn't someone from the VueJS team written this for us? This is a big deal.

PierBover commented 6 years ago

@Soletiq if you really need to display thousands of rows at the same time you will get much better performance with <div> than using tables.

Soletiq commented 6 years ago

It works slightly better but even at 250 rows I start to notice lag in scrolling.

How does no one else find this to be a gigantic issue? How does one build an application with infinite scrolling like a feed of posts, similar to facebook or twitter? The scrolling behavior would feel terrible after only a few hundred posts. The only solution i've found is to generate the HTML myself in an application that uses VueJS entirely. This is a terrible solution.

yyx990803 commented 6 years ago

@Soletiq have you seen https://github.com/Akryum/vue-virtual-scroller ?

PierBover commented 6 years ago

It works slightly better but even at 250 rows I start to notice lag in scrolling.

250 rows is nothing. I've had thousands div of elements with no penalty in performance.

Are you sure the issue is not somewhere else?

Soletiq commented 6 years ago

Its not an extremely simple table, but certainly not advanced either. It has 7 columns that use v-if / else elements and a few number conversions and 2 small 18x18 logo/icon images (removing the images doesn't help).

An equally significant problem when using v-for is when you scroll the bottom (to load more rows), it takes more time each batch to insert the rows into the table since it has to handle an increasing amount of rows. So when you get to a few hundred rows it can actually take 3+ seconds for it to insert the next batch. Whereas, when generating my own HTML, it can load the new rows in half the time.

@Soletiq have you seen https://github.com/Akryum/vue-virtual-scroller ?

I will try this, thank you.

PierBover commented 6 years ago

Could you post a simplified reproducible use case?

Soletiq commented 6 years ago

This is a GIF of me scrolling on the first batch. Scrolling is much smoother here. https://gyazo.com/07e617c350b9d16ecb3e280c71139153

This is a GIF of me scrolling after about 10 more batches of 50 rows each. So about 500 rows here. If you look closely the scrolling is more glitchy, not as smooth. It gets worse as you load more rows. Its also really important to mention that changing other state values is extremely slow. For example, when I click a button that brings up a modal, it simply changes the state from open = false to open = true and this takes about 10-20s with this amount of rows in the table. Furthermore, loading more batches takes increasingly longer loading times. For example, loading the next batch with this amount of rows takes almost 7s, and after that it would take maybe 8s. https://gyazo.com/ee6aef78a441cd8f2d307268fec49582

I've done everything I can think of. My only solution is the most terrible solution which is generating the HTML in javascript.

PierBover commented 6 years ago

@Soletiq could you post a reproducible example?

ethan-deng commented 6 years ago

Put the usability issue aside, I tested the performance of v-for of Vue.js and compared it to React.js. And here is what I found.

Vue.js v-for to render 50,000 lines

<template>
  <div>
    <div
      v-for="n in 50000"
      :key="n">Hello World {{ n }}
    </div>
  </div>
</template>

React.js to render 50,000 lines

const About = () => {
  var lines = []
  for (let i=0; i<50000; i++){
    lines.push(<div>hello world {i}</div>)
  }
  return <div>{lines}</div>
}

Vue.js takes 30 seconds to render and React.js takes 5 seconds to render. Vue.js is 5 times slower than React.js. The worst for Vue.js is that when testing rendering 100,000 lines, Vue.js will just hang the browser (Chrome) and I have to kill the browser process through OS. However React.js can "easily" render all 100,000 lines in about 7 seconds.

To summarize the test results
| Number of Lines | Vue.js | React.js |
| 50,000 | 30 seconds | 5 seconds |
| 100,000 | 4 minutes | 7 seconds |
| 500,000 | forever | 3 minutes |

The conclusion is that React.js has more capacity than Vue.js when rending large amount of data.

Why is this important?

Many application may need to render large amount of data occasionally but the amount of data is within a range. For example a data set may never reaches 100,000. With the capacity of rendering 100,000 lines within 7 seconds, the application will accommodate a lot more scenarios without complex solutions.

I actually came to this issue when rendering just about 2000 components with each of them primary just contains a file name. When adding a one or more complex components such as a Vue material design button to each component, the rendering will unbearably slow and I have to optimize the component without using any advanced component in it in order to let it renders in reasonable time.

Although the ultimate solution for rendering a large amount of data may be using paging or other techniques but in my opinion, capacity is also important. With the capacity like React.js, we can go a lot further without reaching the limitation of the platform.

Akryum commented 6 years ago

@ethan-deng How is Vue performance if you use functional components in your list?

ethan-deng commented 6 years ago

@Akryum I haven't tried that. Could you provide a code to do that? Thanks.

PierBover commented 6 years ago

In my experience rendering lists with about 3000 divs I experienced noticeable better performance with React than Vue. Nothing as drastic as what @ethan-deng is mentioning, but the React app definitely feels more snappy.

It's weird though because in these benchmarks Vue usually comes on top of React: https://www.stefankrause.net/js-frameworks-benchmark7/table.html https://github.com/krausest/js-framework-benchmark/

ethan-deng commented 6 years ago

@Akryum Just tested with this code

<script>
export default {
  name: 'PerfTest',
  render (h) {
    const lines = []
    for (let i = 0; i < 100000; i++) {
      lines.push(<div>Hello World {i}</div>)
    }
    return (
      <div>
        {lines}
      </div>
    )
  }
}
</script>

Feel there are some improvements of performance but not a whole lot.

yyx990803 commented 6 years ago

I'll just say this: measuring performance of rows more than what you can show in one screen is pointless. Is there any practical use case where you need 100,000 rows rendered at the same time? If you have such a large amount of data, 7 seconds render time is pretty much still unusable, and you should be using a virtual list component.

P.S. make sure to test it in production mode.

ethan-deng commented 6 years ago

One use case of this is that a customer want to generate a report which contains all the data which is about 100,000 lines of data. We can wait and then print it out and hand it over to them. Or save it as PDF file using the browser and email the report to them. This only needs to be done once in a while and at the same time our app does support pagination and searching to get access to the data.

Another point is the "capacity" I mentioned, while rendering 100,000 lines of simple data is pointless most of the time except the case above, the framework should support rendering 2000 of more complex components at once without hanging the browser.

Another point, most of the enterprise apps are exposed the "big data" set randomly. At the start of a project, it is hard to predict what is the upper limit of any data set. We cannot afford to put "good design" everywhere in the application.

PierBover commented 6 years ago

I'll just say this: measuring performance of rows more than what you can show in one screen is pointless. Is there any practical use case where you need 100,000 rows rendered at the same time?

Maybe not rows, but there is a case for rendering thousands of SVG nodes, in the same screen.

ethan-deng commented 6 years ago

Our current scenario is just simply listing files under a folder in a web app. There could be 5 files or 5000 files. Does it make sense to let you view all your 5000 pics on your desktop app or you have to page through it?

yyx990803 commented 6 years ago

My argument has nothing to do with React vs. Vue because your 5x perf difference doesn't exist:

There must be something wrong in your setup or usage. That seems to make the whole conversation pointless.

Either way, you should be using a virtual scroll component when you need to render more than 1k items in a list (which you seem to have ignored)

ethan-deng commented 6 years ago

@yyx990803 Thanks for the links. Yes, I don't feel there are too much differences between Vue and React from the links. I took a second look at my project, it seems the culprit is the the 3rd party UI library we introduced, we have

import Vue from 'vue'
import VueMaterial from 'vue-material'
import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'
Vue.use(VueMaterial)

in the App root,

After removing this code, I don't see the 5x perf difference any more. It is good to know it is not the issue of the framework. Thanks!

Akryum commented 6 years ago

I can confirm there is almost no difference between the two with those production builds using vue-cli and create-react-app (100000 elements rendered):

Also for some reason on my Firefox on Windows, Vue is consistently (800ms) faster than React (3500ms)

ethan-deng commented 6 years ago

@Akryum Have you tried 500,000? This is just out of curiosity than practical. It seems Vue sample never renders but I have a result that React takes 164485.100000049ms to render 1,000,000 lines.

Another discovery is that it seems at 500,000 Vue.js just silently stops the effort of rendering instead of hanging the browser but React make all the effort anyway and can get the job done in a reasonable time such as in a minute or two.

Here is in the console when trying to render 500,000 in vue.

RangeError: Maximum call stack size exceeded
    at normalizeArrayChildren (vue.esm.js?efeb:2229)
    at normalizeChildren (vue.esm.js?efeb:2204)
    at _createElement (vue.esm.js?efeb:4404)
    at createElement (vue.esm.js?efeb:4357)
    at vm.$createElement (vue.esm.js?efeb:4492)
    at Proxy.render (PerfTest2.vue?6bf8:10)
    at VueComponent.Vue._render (vue.esm.js?efeb:4544)
    at VueComponent.updateComponent (vue.esm.js?efeb:2788)
    at Watcher.get (vue.esm.js?efeb:3142)
    at new Watcher (vue.esm.js?efeb:3131)
yyx990803 commented 6 years ago

@ethan-deng 500k is simply not a reasonable number for a web framework to deal with. If you have that requirement, just use React. That's fine.

Akryum commented 6 years ago

@ethan-deng Also, a virtual scroller is a perfect solution for your use case. It will make your app render 500k items almost instantly.

ethan-deng commented 6 years ago

Thanks. It is always good to know the boundary of a framework. Also I am using a Vue Material UI library https://vuematerial.io/. Anyone has a good guess why it slows down the rendering so much.

I got this from Chrome debug tool with rendering 30,000 divs

Recalculate Style Total Time 27.43 s Self Time 27.43 s Elements Affected 30007 Pending for 1977.3 ms Initiator reveal First Invalidated createStyleElement @ addStylesClient.js?ae38:114

Parse HTML Total Time 27.58 s Self Time 34.67 ms Range :8080/#/perf:10 [10…-1]

smolinari commented 6 years ago

@ethan-deng - If you are not happy with ElementUI, please take a look at Quasar Framework and see if it fits your needs better. It won't help you with your rendering issue, (which isn't really an issue) but it might in other areas.

If you are going to stick to ElementUI, you'd be much better off asking them about your rendering issue, but they'll more than likely give you the same answer. It isn't an issue or rather your use case is incorrect. :smile: 👍

Scott

ethan-deng commented 6 years ago

More findings related to this issue. The performance difference is actually introduced by referencing the CSS files as

import 'vue-material/dist/vue-material.min.css'
import 'vue-material/dist/theme/default.css'

or in the index.html file. When either the index.html imports the CSS files or using import in the JS file, the "createStyleElement @ addStylesClient.js?ae38:114" will slow down the rendering quite a lot.

After removing any references of CSS file, Vue.js actually shows better performance than React. Vue.js can rendering 10,000,000 divs in about 120 seconds which is better than 180 seconds of React.js

However, since we need to use Vue.js with a material ui library https://vuematerial.io, I tested using Vue.js to render 20,000 raised buttons to using React.js with material ui library https://material-ui.com to rendering 20,000 raised buttons.

React.js with the material ui library is significantly shower than rendering just 20,000 divs but it can be done within seconds however the Vue.js with its material ui library never renders and hangs the browser.

I know it may be the issue of the https://vuematerial.io, but I am worried this may be related to how the style loader is implemented in Vue.

Another finding is that, only using this code will cause the "Maximum call stack size exceeded" but not using the v-for in template

<script>
export default {
  name: 'PerfTest',
  render (h) {
    const lines = []
    for (let i = 0; i < 500000; i++) {
      lines.push(<div>Hello World {i}</div>)
    }
    return (
      <div>
        {lines}
      </div>
    )
  }
}
</script>
yyx990803 commented 6 years ago

Pretty sure CSS performance is a completely framework agnostic problem.

martinandersen3d commented 5 years ago

Here is table creation benchmarks on all JavaScript frameworks

https://github.com/krausest/js-framework-benchmark/blob/master/README.md

Use whatever works in this edge case :)

I purpose: Maybe even write the table in vanilla JavaScript? Then wrap 100 rows at a time in a div, and use a ins. Observer to detect when it is visible and show

https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Https://github.com/Akryum/vue-observe-visibility

Maybe even dynamicly render 100 rows async on demand, so the main thread is not blocked?

Or more optimization: On start, show 50 rows to the user, after render, then render more. Then the user have something on screen.

PierBover commented 5 years ago

Here is table creation benchmarks on all JavaScript frameworks

https://github.com/krausest/js-framework-benchmark/blob/master/README.md

Here are the results of that benchmark:

https://rawgit.com/krausest/js-framework-benchmark/master/webdriver-ts-results/table.html

kyleparisi commented 5 years ago

I came upon this issue in search of why my 100 row table (~1500 cells) was lagging when sorting a column. The data set was only 3500 rows. Although this is not directly related to the above descriptions it is worth noting that I had fallen for the https://vuejs.org/v2/guide/list.html#v-for-with-v-if logic gotcha. I had made this mistake on an element which would not render anything but still loop the whole data set.

mathieu-gilloots commented 5 years ago

Same problem here, I try to display a card board, with a lot of card (responsive) sometime I've 2 card per line some time 3 or 4, depending of the screen. With a big client we have a lot of card and Google Chrome crashes after a freeze time.

If you do a v-for on a large number, there seems to be buggy.

We can't use virtual scroller because our client need to be able to print the page with Control + P and we have a PDF renderer service that do the same things.

Any ideas ?

PierBover commented 5 years ago

Any ideas ?

Vue, React, etc, are intended for reactive DOM manipulation which is why there is an overhead on each element and that can become problematic on extreme cases like yours. Personally I've rendered thousands of SVG elements with Vue and never hit such a serious a problem.

If you only need to paint DOM elements you could simply render in the server or use browser APIs bypassing the virtual DOM.

If you do need reactivity, and assuming there is nothing wrong with your Vue code, maybe you could try with Inferno. I've found it's faster than Vue on some use cases.

ethan-deng commented 5 years ago

This could be performance issue of CSS rendering. I tested vue + vue UI a library and react + a react UI library. Vue is slower than react, but if I test vue vs react without any third party UI library, vue out-perform react.