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

Performance issues with a large number of views #1648

Closed
stevenhauser opened this Issue Jan 20, 2015 · 23 comments

Comments

Projects
None yet
9 participants
@stevenhauser
Copy link

stevenhauser commented Jan 20, 2015

Hello, sorry for the upcoming book, just trying to provide some thorough data:

There appear to be substantial performance issues when there are a significant number of views. The problem looks to be around initialization time plus render time. Initialization seems to take up nearly twice as much time as actual rendering.

Compared with React (for instance), there is very low overhead for instantiation, and rendering time takes up the majority of CPU time. Rendering times between React and Ractive seem comparable, with React just edging out Ractive—it's the instantiation time in Ractive that's the perf killer.

Running through the initialization code, I didn't come across much to improve performance. The problem seems to be around many "recursive" Fragment initializations, where each Fragment initializes its child Fragments which ends up taking a substantial amount of time.

I was able to improve performance a bit by refactoring the wait function to push onto an array of callbacks rather than setTimeout for every callback, but the main issue still seems to be around initialization.

Do you have any ideas on how this could be improved? As mentioned, I was unable to find much on my end.

Also unsure whether this performance issue a concern to the Ractive project, or do you suspect that the average use case has far fewer views?

Here are some charts from the dev tools comparing Ractive performance vs React's, each rendering 500 views. Note that I'm not exactly trying to compare the two, only trying to show that when there are a lot of views, performance deteriorates with Ractive, but pretty good performance is possible, especially if init time can be cut down.

Ractive performance example
ractive-perf-chart-annotated

React performance example
react-perf-chart

@Rich-Harris

This comment has been minimized.

Copy link
Member

Rich-Harris commented Jan 23, 2015

One thing I've consistently found while developing Ractive is that things often start out as minor hypothetical concerns, but become real issues as people develop more and more ambitious apps. I found this myself the other day - we had to very quickly cobble together an app that had hundreds of components, and there was no time to do any real optimisation. Performance on desktop was totally fine, but on an iPhone 4... ouch.

So yes, performance is something to keep an eye on. The edge build has a number of performance improvements (in my testing, it overtakes React in some cases where React previously won - I find they're neck and neck in a lot of situations), so you could try that.

One particular thing to note is that the latest stable build (0.6.1) has an annoying bug introduced by that version's build process, whereby Ractive's own Promise implementation would be used in favour of native implementations due to variable shadowing. Because Ractive's implementation was designed for compatibility and filesize rather than performance (hence the somewhat reckless use of setTimeout - see #1642 for a discussion on using RSVP instead), the fact that it stomps native promises is a bit of a nuisance.

Oops - it turns out that the edge build is still using the polyfill instead of native promises! Which I guess means we have some performance improvements to look forward to :-)

  • use native promises where possible
@stevenhauser

This comment has been minimized.

Copy link
Author

stevenhauser commented Jan 23, 2015

@Rich-Harris thanks for the feedback. I did try the edge build before posting this but actually found that performance was decently worse, but I assumed that some kinks were still being worked out and I intended to wait until the "official" 0.7.0 release. Let me know if you want any additional info on my findings there.

@Rich-Harris

This comment has been minimized.

Copy link
Member

Rich-Harris commented Jan 24, 2015

Ah, interesting. There are some kinks still being worked out, yes, though all info is helpful - is performance worse at initialisation, or subsequent updates?

The Promise bug has been fixed upstream, so the edge build will now use native promises where possible. Hopefully that will improve initialisation a bit.

@stevenhauser

This comment has been minimized.

Copy link
Author

stevenhauser commented Jan 24, 2015

Let me do some fresh tests in the next day or two and I'll try to get some more info back to you.

@kurdin

This comment has been minimized.

Copy link

kurdin commented Jan 30, 2015

@stevenhauser from your Ractive performance example I removed <view> and use container template with SVG code directly. This improves render speed by 2: http://codepen.io/anon/pen/YPxOEQ

You example http://codepen.io/team/articulate/pen/PwpJzN/ results:

creating 500 views: 520.059ms
pen.js:14 Profile 'creating 500 views' started.
pen.js:27 Profile 'creating 500 views' finished.

Version without <view> template http://codepen.io/anon/pen/YPxOEQ results:

creating 500 views: 221.364ms
pen.js:12 Profile 'creating 500 views' started.
pen.js:25 Profile 'creating 500 views' finished.

I understand, that you can reuse <view> template inside others, so maybe this means nothing. Just testing. :)

@stevenhauser

This comment has been minimized.

Copy link
Author

stevenhauser commented Jan 30, 2015

@kurdin Yeah to your last point my intention was to illustrate that building up a lot of components has a significant performance impact, as well as adding more nodes or mustaches to a particular template. And I think in a real world use case, larger components are usually built up from smaller components which exacerbates the issue.

@kurdin

This comment has been minimized.

Copy link

kurdin commented Jan 30, 2015

@stevenhauser , yes I see your point and I think this is true.

With React you did not use templates (JSX) at all and directly created DOM with JS. With Ractive there are mustache templates what needs to be compiled first.

Nope, same results with compiled templates in Ractive: http://codepen.io/anon/pen/gbxdVd

creating 500 views: 574.397ms
pen.js:79 Profile 'creating 500 views' started.
pen.js:92 Profile 'creating 500 views' finished.
@PaquitoSoft

This comment has been minimized.

Copy link

PaquitoSoft commented Mar 2, 2015

Hi,

I'm also having performance problems when rendering views, in my case, when rendering server-side.

In our project, we would like to move from an always server-side html generation strategy (using dustjs-linkedin) to an isomorphic strategy using Ractive (For The Win).
When we changed our templates syntax an rendering engine (we use expressjs), we found that Ractive is very much slower than dust.
The same view (a products category view showing a large navigation menu and 150 products aprox.), has an average of 83.10 milliseconds to render in my workstation the dust version and an avarage of 364.90 milliseconds the ractive version.

The render code for the ractive version is like this:

var t1 = process.hrtime(),
    r = new Ractive({
        template: template,
        data: options,
        partials: partials,
        components: components,
        stripComments: false 
    }),
    html;
logger.debug('Renderer::render# Time to create ractive instance: %d (micros)', process.hrtime(t1)[1] / 1000);

t1 = process.hrtime();
html = r.toHTML();
logger.debug('Renderer::render# Time to generate HTML: %d (micros)', process.hrtime(t1)[1] / 1000);

I also noticed that object instance creation takes almost all the time (90% aprox).

I don't know if there's any specific properties I should use for server side rendering, as I only need the template to be converted to HTML using the options passed, so I don't need all the ractive data every tracked DOM element holds.

We can't afford that amount of time spent rendering our views but I truly want to use Ractive in our project as I found it a super useful and easy to use library.

Do you have any hints in order to improve server-side rendering time?

Thanks.

@martypdx

This comment has been minimized.

Copy link
Member

martypdx commented Mar 2, 2015

@PaquitoSoft Approximately how many component instances are there in the rendered graph? That's probably the biggest thing that's slowing you down. One thing is to make sure to not have unresolved data, give it default null value or whatever, but make sure it resolves immediately. Also, partials will run much quicker than components if you don't need the full encapsulation logic of a component.

Also, what Ractive version are you using? Edge probably is quicker that 0.6 but not by much. Currently working on some potential future changes that will more specifically address the component speed gap (#1740), but that would be 0.8

@PaquitoSoft

This comment has been minimized.

Copy link

PaquitoSoft commented Mar 2, 2015

@martypdx I'm not sure if I understood your question, but I only use one Component in my view (for dealing with template inheritance #1679).
I'm using the edge version which is the one supporting named yields.

<MainLayout> {{! This is the component which describes the main layout }}
    {{#partial customStyles}}
        <link rel="stylesheet" href="{{ staticUrl('css/category.css') }}" media="screen" />
    {{/partial}}

    {{#partial mainContent}}
               ........

If you're asking for the html nodes in the resulting HTML, we have 2.000. If I remove our large menu navigation (for testing purposes), it gets down to 1.400 and the time to render lowers to 153.80 milliseconds (avarage rendering time).

UPDATE: I know that Ractive is not a template engine, it's much more; but in the context of server-side rendering we don't need all the data-binding stuff. Maybe it's crazy to assume that something can be done to tell Ractive to only deal with HTML generation, but I think that's what would be needed to improve server-side rendering time. I'm also aware that maybe this is only my case, but I'm chasing a scalable isomorphic application.

Thanks.

@PaquitoSoft

This comment has been minimized.

Copy link

PaquitoSoft commented Mar 4, 2015

I've been debugging ractive instances creation and found that almost all the initialization time is consumed creating fragments.
This section of the initialiseRactiveInstance function:

ractive.fragment = new Fragment({
    template: ractive.template,
    root: ractive,
    owner: ractive, // saves doing `if ( this.parent ) { /*...*/ }` later on
    cssIds: cssIds
});

Following the stack, I found that Fragment$init function is called and it gets its time consumed in this section:

this.items = options.template.map(function (template, i) {
   return createItem({
     parentFragment: _this,
     pElement: options.pElement,
     template: template,
     index: i
   });
});

As Ractive uses Mustache-like template syntax, maybe a function could be created to render HTML from a template running that template (and partials and components) with some options with Mustache but adding to it the sugar you've added to Ractive templating syntax.

What do you think?

@Rich-Harris

This comment has been minimized.

Copy link
Member

Rich-Harris commented Mar 4, 2015

@PaquitoSoft Unfortunately it doesn't really work like that - Ractive works by constructing a virtual DOM under the hood which stores references to real DOM nodes when it renders, whereas Mustache is just generating a string without any understanding of its contents.

There might well be some optimisation opportunities around fragment creation though - it's a useful place to start looking. Thanks.

@Rich-Harris

This comment has been minimized.

Copy link
Member

Rich-Harris commented Mar 4, 2015

@PaquitoSoft Also, thinking about the server-side rendering use case, we might be able to eliminate the data-binding overhead since that's unnecessary (i.e. hydrate the virtual DOM lazily at toHTML() time)

@stevenhauser

This comment has been minimized.

Copy link
Author

stevenhauser commented Mar 4, 2015

For what it's worth I can 🌽firm that I reached the same 🌽clusion as @PaquitoSoft. 🌽.

@PaquitoSoft

This comment has been minimized.

Copy link

PaquitoSoft commented Mar 4, 2015

@Rich-Harris Thanks for your explanation. I already tried to comment out some code in the fragments creation regarding things not needed when rendering server-side, but the time spent still was too high. As you say, you're generating a Virtual DOM which involves creating too many objects representing DOM nodes (about 2000 in my case). I think it's the generation of that many object which slows downs rendering time in the server.
I'm aware that it is not easy to modify ractive to be server-side rendering efficient as that's not its main purpose. Maybe it would be a better idea to develop another tool specific to create HTML from Ractive templates, getting Mustache functionality and adding your extensions (array indices, object iteration, components,...).
What do you think about this idea?

@Rich-Harris Rich-Harris added this to the unknown/unspecified milestone Mar 14, 2015

@simonvizzini

This comment has been minimized.

Copy link

simonvizzini commented Jun 18, 2015

tl;dr I'm having performance issues, but unfortunately I cannot really demonstrated the issues I have. But if someone could take a look at the fiddle I posted and see if I'm doing any fundamental mistakes in how I use/plan to use ractive then that would be great! I also discovered issue #1740 during my research so I tried the kftw-staging branch, but performance is about the same as edge/0.7.3 in my case.
/tl;dr

I'm currently playing around with ractive and to make things more interesting I decided to re-implement an existing tooltip we have with ractive, simply because the current implementation has a monstrosity of a view model which grew over the years and this is an ideal candidate to split up into smaller ractive components and templates. We have a grid and this tooltip is shown every time a user clicks on a row, showing information related to the clicked row.

Working with ractive was a real joy so far because I could split up many things into small components with their own logic while still having access to the root components data. Very nice!

But the more components, expressions and {{#if}} blocks I add the slower the initialization time becomes, which kind of surprises me because so far I've only implemented the main component with a total of 5 unique sub-components and a bunch of expressions and if's (and I'm far from done yet). I'm initializing the component every time on row click and tearing it down when closed. We do this because we don't want to have unnecessary views alive if they are not used/shown.

Alright so according to the Chrome profiler it takes around ~350ms before the tooltip is initialized and rendered. This delay is noticeable and makes things not feel responsive. I tried to setup a fiddle to mimic the same "complexity" with regards to sub-components and references/expressions, but it's really difficult to come up with a simplified and non-sensitive example of our actual use case. So the fiddle I'm about to post is nonsensical and doesn't really represent the performance issues I have in my actual implementation, but it should give at least a slight idea of the pattern I was planning to use for future components.

Here is the fiddle: http://jsfiddle.net/j8Lfxa2w/
You will notice that all the computeds and methods of "item" don't make any sense at all, I just wanted to have a bunch of differently named methods/properties I could reference in the template. Also this fiddle doesn't perform that bad on my PC, something between 50-80ms, but again this is just to demonstrated on how I was planning to use ractive templates and components. Please let me know if you see any anti-patterns :) I'm sure there will be some...

The main difference between my actual implementation and this fiddle is:

  • The actual data model ('item' in the fiddle) is quite complex, but nothing too crazy. Our actual model has around 40 properties (primitives and dates only) and additionally around 30 methods that do some basic evaluations and calculations, but nothing CPU expensive at all (can confirm this in the chrome profiler). No arrays/lists are being rendered.
  • The actual templates contain more HTML, around twice as many references inside mustaches, around twice as many {{#if}} blocks and each component has a few more computeds which are of course also referenced in the templates.

I tried to figure out which parts of my components/templates could be problematic but I'm not very good at profiling and ractives code base is too huge to make any sense of the callstack.

@Rich-Harris

This comment has been minimized.

Copy link
Member

Rich-Harris commented Jun 18, 2015

ractives code base is too huge to make any sense of the callstack.

Yep! It's definitely grown some warts. Last week I started a major spring clean, building on #1740, that I'm hoping will get that under control - many fewer modules, less indirection. It's not quite ready to share (~25% of tests currently failing), but if it pans out then profiling ought to get a little easier. This example doesn't currently render correctly on my local branch, but if I can fix that I'll report back with anything I find.

The only thing I can suggest is being explicit about inter-component mappings. By default, Ractive will map item inside each component to item outside it - you can save it a bit of work by doing e.g. <TooltipHeader item='{{item}}'/>. It didn't make a huge difference on this machine and with this example (it renders in about 11-13ms with or without explicit mappings), but I've seen it make things a little bit quicker in other examples.

@simonvizzini

This comment has been minimized.

Copy link

simonvizzini commented Jun 18, 2015

Awesome, that's great news! I really like ractive so far, it makes developing UI widgets/components a breeze. Great work, everyone!

My fiddle honestly isn't a good example, and what I ultimately wanted to do is to reproduce the same component and template structure in an isolated fiddle, because the callstack in my actual implementation doesn't look right, I believe I hit some combination that ractive is struggling with, for example there are hundreds of recursive function calls which I'm unable to reproduce in my fiddle so far. The callstack is reeaaally deep which makes me believe I might have created a circular reference somewhere or something, but then again the call stack doesn't exceed and everything renders within 300ms so I don't know. I was unable to spot a logical error and the complexity of my component structure doesn't seem to be something out of the ordinary.

Anyway, I will experiment a bit more tomorrow and try to better reproduce my actual situation, but then again maybe this will become a non-issue after your spring clean :)

@Madgvox

This comment has been minimized.

Copy link
Member

Madgvox commented Jun 18, 2015

Perhaps you can post a screenshot of the callstack? There might be something in there that reveals what's going on in your templates.

@daveobike

This comment has been minimized.

Copy link

daveobike commented Feb 15, 2016

I came upon this issue after running out of optimization options for my ractive project view initialization. I am currently working on an alpha release platform that utilizies ractive version 0.7.3. It's a card based UI that I dont think is overtly complex (to be determined hopefully from the ractive community). My initial view renderings end up taking approx 150 to 200ms per card rendered(using Chrome profiling tools). I don't allow for more than 10 cards(components) but that still results in a ~2sec total render time that is all coming from the recursive Ractive render initalisation, as alluded to from earlier comments. Total render times scale with the number of initial card components. So if I am only rendering a view cards it's not too noticeable of a delay.

The recursive calls all seem to follow the section and component structure of my template, it just takes time(each card component is fired from auto magic iterative #each section that runs over a nested object list from the data). I am partly supiscious of the number of "computed" (actual data functions since the primary template is iterated over an object list) functions. But each of those data functions are simple one line abstractions to an underlying local object call.

I am including a NON-working codepen so other cans see the level of complexiting of my Ractive instance and associated template(I show this in the html section of the codepen but in production it is a separate script file with all templates, partials combined in a single file.) The codepen link: [Link Removed]

I would appreciate any feedback on whether the ractive init times are to be expected with the amount of nodes etc from my component and template setups. Or, is there any further suggestion on how to get the render times down given my template structures.

Thanks and I appreciate the help

@evs-chris

This comment has been minimized.

Copy link
Contributor

evs-chris commented Feb 16, 2016

@daveobike without a working example, it's a little harder to look into specific performance issues. But I can say that in examples earlier in this issue, edge takes roughly half the time of 0.7.3. I've also been looking into ways to improve performance recently. I haven't looked into components recently, so there's a chance there's a good amount of performance to gain for edge.

One thing that you can do that usually gives a little performance boost in moderate to heavily nested contexts (lots of sections) is to remove ambiguity from your references. For instance, if you have a root-level chunk of data that you're referencing deep in a template, using ~/someObj to access it vs someObj will be a fair amount faster.

@daveobike

This comment has been minimized.

Copy link

daveobike commented Feb 18, 2016

@evs-chris thanks for the follow up and suggestions. I have a couple of other minor issues that are supposed to be cleared up with the edge build. I think I will take that route and revisit profiling/optimization when I have a bit more breathing room with my production schedule.

@evs-chris

This comment has been minimized.

Copy link
Contributor

evs-chris commented Nov 5, 2016

Well, 0.8 is out and considerably faster than 0.7, which wasn't quite out when this issue opened, so I'm going to close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.