Ember is very slow at rendering lists

Ember seems to be very slow at rendering lists. To investigate I’ve made 3 versions of a page that toggles rendering 2,000 span elements:

They are implemented as similarly as is practical. Unsurprisingly plain jquery is the fastest. On my computer it renders in a fraction of a second despite dong each append individually. Angular takes just less than a second. Both are very fast at removing the items.

Ember takes about 3 seconds to add and 2 to remove. See updated figures

Using the unbound helper makes no appreciable difference.

I started investigating because I have an app that renders a large but not ridiculous list of links and navigating to or from that route was causing a painful delay, so I think this has real world impact.

What can we do to improve performance?

6 Likes

I have seen the same when rendering large(ish) lists with Ember. Things seem to be O(n2) (or worse) when it comes to lists.

I tried out Ember List-view which is cool, but still not mature enough to use. I ended up doing my own lazy renderer by using the slice() function to only show part of the list in the view and continous scrolling. It works pretty good, but there are defenitely performance issues in Ember that must be adressed.

Also are you using Ember data? I have seen that just loading a large list into ember data with find() is very slow and at least quadratic.

@mattiasl ember-data’s performance is another issue, it seems to be being worked on here - https://github.com/emberjs/data/pull/800

I’m also using slice to reduce what has to be rendered, but it’s not ideal.

By the way, it’s not all bad news - https://github.com/gunn/insanely-big-tables#angular-vs-ember but there’s certainly performance room to grow into.

This is a huge problem. Scalability is one of the major features being touted as an advantage over Angular. There’s been all this talk from Tom Dale about how Angular’s dirty checking is slow compared to Ember’s accessors, and that Ember is ultra smart and efficient and updating bindings. That’s the tradeoff for having to type .get(‘property’) all over the place.

And yet here we are, revealing that Ember lags behind on the most simple case of rendering 2,000 spans. Some may claim that this is not real-world enough. Well, I challenge you to make a modification to the most highly visible sample projects that compare Ember and Angular - TodoMVC. In the respective architecture-examples, bootstrap the code to store 2,000 todo items in local storage, and let the apps pick them up and render. You’ll see that Angular loads them in less than a few seconds, and can switch between the filter tabs just as fast. Then, do the same with ember and it’s local storage adapter, and watch it choke for 2 minutes straight just trying to render the initial tab. Good luck trying to switch between the other filtered tabs.

So you might say that Ember Data is the culprit here (even though that is a lame excuse considering it’s included in the sample). Well, I went ahead and stripped out ember data completely, replacing with a vanilla Ember.ArrayController and Ember.Objects, only to watch it still significantly lag behind Angular. And to add insult to injury, the code looked like a mess because of needing to convert to/from Ember.Object and native js objects before saving to local storage. All the while Angular’s code is sitting there, simple and clean.

Perhaps I’m doing it wrong. Maybe the Ember ToDo app written by Tom Dale and Addy Osmani is so outdated, that it needs to be rewritten. But at such a critical pivot point, it’s not looking good for Ember.

Well, let’s start the comparison off right: here’s a JSBin using the minified RC6 of Ember and Handlebars: http://jsbin.com/enapec/21/edit (the provided Angular jsbin was already minified). Using the minified Ember alone makes a pretty huge difference, though it’s admittedly still a bit slower than Angular for this particular example.

Beyond that, there’s also considerations as to how quickly items can be removed at random from large arrays, how quickly angular notices/responds to such changes, and what sort of overhead is incurred when a property(ies) on various list items change. I don’t know enough angular to set up a proper benchmark (someone want to do this?), but given that Angular has no computed properties and no way to specify data dependencies, if any values on $scope changes, all data bound to values returned from functions must be re-rendered, and those functions re-called, whereas Ember will only re-call the computed properties / bindings that were invalidated by the change in data and only re-render the portion of the DOM that was affected. Not the easiest thing to benchmark, but depending on how data comes back from the server in bulk and makes changes to certain properties on certain items, you might start feeling the difference.

@sjmueller I dunno how much perf work has been done, since RC1, but the Ember TodoMVC’s is RC1 (4 months ago) and Angular’s is 1.07 (1 month ago). Might make a difference.

But anyway, there’s always more room for perf enhancements even if Ember’s behind on certain benchmarks. In particular, when HTMLbars comes out, we’ll be able to get rid of metamorphs (the script tags that mark off individually updatable regions of the DOM, which you can see for each iteration of #each), which will yield some major perf enhancements. This probably won’t be a few months though.

In the meantime, you can use {{unbound item}}, when it makes sense, to speed stuff up, or try a big DOM approach like https://github.com/emberjs/list-view

3 Likes

I have experienced similar lag in loading up to 4K items in a list. I ended up using this enhanced version of ebryn’s ember list view, which adds several features and integrates some of Addepar’s table, and performance was of course fantastic since it’s re-usable views. Not to distract from the valid perf concerns, but I would recommend giving it a look. https://github.com/realityendshere/emberella#views

1 Like

@machty so using minified sources helps, that it good to know! I’ve updated my original post.

Regarding your second paragraph, yes I agree there are more involved cases and ember performs better in them which I pointed out with my insanely-big-tables link.

HTMLbars is good news and I guess the answer to this thread. We’ll just have to be a bit patient. Is there anything we can do to help?

People might find these gists about HTMLbars of interest: https://gist.github.com/wycats/5808149 https://gist.github.com/wycats/9144666b0c606d1838be

1 Like

@machty, I made sure to update Ember to RC6 in my modifications, and the slowness still remains.

I have been in discussions with other architects within our org about the pros and cons of Ember vs Angular, because we will be adopting just one to move forward. We contrasted a few main points:

Learning Curve - Angular Performance / Scalability - Ember Mindshare - Angular Documentation - Tie Testing - Angular

Now it is true that performance / scalability are extremely important to us, especially when dealing with big data on a regular basis. But if that point can’t go in favor of Ember, then there is little reason at this point to move forward with it. I’m pretty passionate about the subject because I’ve been building a few apps with Ember over the past 6-8 months, and have been very patient when dealing with the instability and changing API. I stuck with it because I believed in Ember’s eventual advantages, but they are getting harder to see nowadays when comparing against Angular.

I’m going to continue to modify the Ember TodoMVC app to see if I can fix the lagging performance with a large list of filtered items. If the gains aren’t there, then it may be time to focus development efforts towards Angular within our org.

@sjmueller can you link to where you’re experimenting with the TodoMVC app?

My buddy @mixonic created this JSBin to highlight some ways you can hand-render if it makes sense for your needs.

http://jsbin.com/enapec/34/edit

The items aren’t bound, but it’s a lightning fast, and there’s a middle ground between totally-bound and the zero-bound approach in this jsbin.

If you think about what Ember is actually doing to render that code, you can understand why it is slow. You’re creating 2k view instances, and rendering 2k templates. Templates that for the most part are doing very little. Especially if you don’t care about data binding.

For a first stab, let’s stop rendering through templates. This code uses itemViewClass to render each item with a custom view instead of the view used internally by each.

// Use with {{each item in items itemViewClass=App.SpanView}}
App.SpanView = Em.View.extend({
  render: function(buffer) {
    buffer.push("<span>"+this.get('content')+"</span>\n");
  }
});

JSBin: http://jsbin.com/enapec/35/edit

With render over-ridden, we need to interact with the render buffer ourselves.

Even faster would be getting rid of the view entirely. I think there are two ways to do this. You could create a custom view with a render method that loops over all the items, and pushes each element onto the buffer. I think given the previous example you can get that going yourself.

Another simple option is to use a helper. A dumb helper like this is more difficult to wire up for re-rendering when the list changes, but sometimes it is the right solution.

// Use with {{eachInSpan items}}
Em.Handlebars.registerBoundHelper('eachInSpan', function (items) {
  return (
    new Handlebars.SafeString(
      items.map(function (i) {
        return '<span>'+i+'</span>';
      })
    )
  );
});

Live JSBin: http://jsbin.com/enapec/34/edit

Lastly, you could do this in jQuery with didInsertElement and the afterRender queue. I don’t recommend it though.

Breaking it down.

I’m trying to do two things here, so please don’t jump down my throat on “this doesn’t address Angular vs Ember”. First and most of all, I’m trying to help find a real-world solution for @gunn’s problem. Because that is the important thing, and fixing those real-world problems help us build a better framework. So @gunn let me know if any of this is a good fit, and I’ll happily brainstorm some more options. Perf is fun stuff.

My second goal is to demonstrate that though Ember doesn’t magically just make everything always work fast, it does provide you with understandable and flexible tools. If we’re talking about performance, then we aren’t talking about normal anymore. You need to start thinking about what Ember is doing and what the browser is doing. The APIs for Ember internals are fantastic, and this is exactly the kind of time to use them.

4 Likes

FYI here is the RenderBuffer API. Newb member status is trolling my link count.

1 Like

@mixonic Really aweomse insights! I’ve been having similar issues with rendering large lists. Where your solution breaks down for me is that although I don’t need much in data binding, I do need to handle {{action}} for the items in the list being rendered. A contrived example is you have a large list of posts and you need buttons to flag or approve them.

Right now I’m using itemController= which I’m sure is even worse performance wise because I’m creating a new controller for each item in the list.

Any thoughts?

I definitely think the message I’m trying to communicate is getting lost. I see several advanced techniques that help with performance here, but that doesn’t help when trying to defend Ember on performance.

Here’s the problem: When you take the implementations of the Ember and Angular Todo examples and simply inject a few thousand rows, Ember chokes indefinitely. These sample apps were written in idiomatic fashion by respected devs who are proponents of each respective framework. The idiomatic Angular code is performant, and Ember’s is not. Developers need to be able to look at these sample apps and clearly see Ember’s advantages.

Keep in mind, I’m looking at this from a team perspective, rather than of an individual developer. If I ask a junior developer to go code javascript with an MV* framework, I would like the highest probability that he/she will overcome the learning curve and write clean, performant code. We know that Angular is easier to pick up, but Ember is suppose to pay dividends as a project grows in complexity, data, etc. There should be evidence of this thesis here.

@gunn I’ll post my updated Todo app when I think I’ve juiced all performance. I’m trying to beat or match the angularjs-perf example with 2K records, but without losing databinding or readability.

@mixonic those are some interesting techniques for if someone hits a performance wall with ember and wants to push further. The first one especially is good because it keeps the bindings working. Here’s an improvement - http://jsbin.com/enapec/48/edit . By setting tagName to ‘span’ it renders like it should and outputs fewer elements.

The second example is less interesting in my opinion because it’s getting close to doing it all manually and you might as well use jquery on didInsertElement.

I don’t really need help fixing my specific problem because I know I can drop down to a lower level (or render fewer elements) if need be. My thinking is along the lines of @sjmueller’s that it’s a bad thing that ember’s idiomatic approach is not so performant and that’s what I’d like to see improved.

Something that would be useful here would be a good way to do profiling. I couldn’t think of a good way in my original post of comparing ember and angular, but while experimenting I thought of this:

var lastTime = new Date();
var timer = function() {
  var elapsed = (new Date()-lastTime);
  if (elapsed > 100) console.log(elapsed/1000.0+"s");
  lastTime = new Date();
  setTimeout(timer, 0);
};
timer();

Using that we can get some actual figures:

  • Plain angular:
    • add: 0.504s
    • remove: 0.134s
  • Plain ember:
    • add: 1.408s + 0.2s
    • remove: 1.449s
  • mixonic’s SpanView approach:
    • add: 1.002s
    • remove: 1.201s
  • ember with #group block helper:
    • add: 0.200s
    • remove: too fast!

So, the SpanView approach approach is noticeably faster. More interesting is the #group block helper version. It uses the helper from https://github.com/emberjs/group-helper you can see it running at http://jsbin.com/enapec/49/edit . It redraws the entire list if any of the underlying data changes, but would be very fast for mostly static lists.

1 Like

Group helper! Excellent idea to bring that up. The helper itself is very simple, most of the logic backing it is in Ember proper. Read the helper itself, it is very short (would link but newb status is trolololoing me).

And making something custom with it isn’t so bad:

// Make a view grouped with
//   App.MyView = Em.View.extend(App.GroupedViewMixin);
App.GroupedViewMixin = Em.Mixin.create({
  init: function() {
    this._super.apply(this, arguments);
    this.set('templateData.insideGroup', true);
  }
});
// Use directly with {{view App.GroupedView}} or a block
App.GroupedView = Em.View.extend(App.GroupedViewMixin);

JSBin: http://jsbin.com/enapec/69/edit

@gunn the docs on group explain that you can use an itemClassView on each and cause just that row to re-render. It is a little more flexible than always redrawing the whole list for any change on a specific row. If any change to the array itself it made, the whole things will need to re-render regardless of if you use itemClassView.

3 Likes

Ok guys, I’ve optimized the ember todo application so that it is reasonably close to performance with angular-perf when injecting 1000 todo items. You can follow along with my forked repo here:

https://github.com/sjmueller/todomvc

In the ember project, I updated all dependencies and ripped out ember data completely.

In both projects, I’ve made these changes:

  • added 1000 todo items (with extra properties) in a file called data.js, and injected the data on load
  • added a computed property to the respective models
  • updated views to render more properties, included the computed property

If you want to just jump in and see the difference, I’m also hosting the changes:

http://todomvc.samuelmueller.com/emberjs

http://todomvc.samuelmueller.com/angularjs-perf

I feel like this is a good starting point, but we still need to figure out why ember is less performant in both loading the initial lists and switching through the filtered tabs. If anyone wants to jump in with suggestions or updates to the fork, then please feel free to do so. Just remember that we need to keep it idiomatic, because this is a comparison to angular’s idiomatic performance. It wouldn’t be fair to come up with obscure hacks that are outside of normal ember development patterns, or sacrifice important features such as databinding.

First off, it’s not fair to compare Angular and Ember purely on performance. If your organization is making a decision based solely off of today’s performance that seems bad.

@kris and I have tended to be the core team members that focus on performance. We have tons of ideas and strategies for improving performance, but we’re time constrained. There’s nothing fundamentally flawed about Ember that keeps us from continuing to improve the performance. In fact, I’d say we have the right abstractions that makes it extremely easy for us improve performance without end users having to make any changes. I’m not so sure Angular could say the same.

2 Likes

@mixonic That sounds handy, but your jsbin example’s not working.

@ebryn Well that does sound like good news. Could you or @kris put some of the ideas into a gist or similar so other people can have a go at implementing them? Sure it makes sense to consider future performance, but the angular team are doing well and have smart people too.

A good start to making improvements would be to expand the benchmark suite: https://github.com/emberjs/ember.js/tree/master/benchmarks

Are the results tracked in any way?