View rendering performance - Ember v. Angular

@tjohn and I have been comparing Angular and Ember over the last week and were quite surprised at the performance difference when rendering large lists. The TL;DR from our findings was that Ember is roughly 5x slower than Angular.

I am aware that when rendering large lists the recommended solution is to use List View. However List View is cumbersome to use when items are variable size or displayed in rows and columns. We think that lists of the same size should at least have comparable performance.

Our Process

We’ve built a sample application that renders a list of items as similarly as we think possible. We captured metrics for rendering lists of the following sizes (10, 100, 1000, 10 000). The metrics were captured via the timeline in the Chrome developer tools.

These were the steps we took

  1. Enter the number of items in the textbox
  2. Start recording
  3. Clicked “Render” button
  4. Stop recording when all the items are displayed
  5. Look at the click event and record the CPU time

Our Thoughts

This may not be a fair comparison because Angular attaches each item into the DOM one by one. This means that the browser takes longer to layout. We think Angular could optimize this by building a fragment of all items before inserting them. The key thing to note is that Angular allocates much less memory and performs fewer garbage collections during the rendering phase.

For example when rendering 10 000 items the following images of the chrome dev tools highlight the difference in memory allocation and cleanup. The current Ember implementation is very memory intensive as shown by our investigation.

Ember:

Ember

Angular:

Angular

Current Implementation

We dived into the Ember rendering pipeline and noted that it is based on string concatenation . As a collection view enumerates its children, it generates a string representation of each child view and appends them to the buffer of the collection view. The string buffer is then converted into to a document fragment before appending itself to the DOM.

Proposed Solution

We are proposing that the render buffer should be a document fragment. Some benchmarks indicate that document fragments are the fastest way to build and append a DOM tree.

Feedback

As we only have a surface level knowledge of the Ember rendering pipeline are there any reasons the original buffer was not implemented as a document fragment? And do you see any flaws with our proposed solution?

4 Likes

I’m having this issue in my app, where rendering large DOMs kills the garbage collector. Would love to see some improvements in this area, for now we are using pagination, but it still feels much slower than it should be.

Can you try re-running your benchmark with the production build of Ember?

Ember’s development build comes with a lot of asserts (including ones in Ember.get) that slow down development in exchange for much better error messages.

Also, we’re actively working on reducing the amount of garbage by building DOM instead of Strings.

You can see some of the work in progress at https://github.com/tildeio/htmlbars and we’re slated to roll it in in 1.1.

1 Like

Hah, I had no idea that the minified versions were production/release builds. Probably just a javascript standard I’m unaware of, Good to know.

Thanks for the heads up on the production builds. I wasn’t aware the minified builds offer performance improvements either. Are there any other optimizations in the production builds that we should be aware of?

We ran the benchmarks again and can confirm that the performance is much better. On average Ember was about 3x slower than Angular when rendering long lists. We’ve updated our spreadsheet to reflect this.

I’m glad to see work is being done to improve rendering performance. Can’t wait to poke around htmlbars.

htmlbars is not just about performance, it helps improve the syntax of helpers, since helpers will now have DOM context, allows us to move away from <img {{bindAttr src=url}}> to just <img src={{url}}>

https://github.com/tildeio/htmlbars/blob/master/tests/combined_ast_tests.js#L64-L99

As @wycats mentioned, there are a lot of improvements coming in this department but I thought I’d jump in with some thoughts on client performance.

Right now, if you throw 10k rows at Ember without thinking about it, you won’t get good performance for free. I am not convinced this is a problem outside of synthetic benchmarks, but I’d love to be proven wrong with a real world example.

Having said that, it’s not impossible to deal with that much data in Ember, you just have to think about it. People often tell us Discourse is a fast app but I honestly haven’t spent much time making it faster. We have nice little rendering statements that output how long in ms a view is taking to render. If we find something is rendering slowly I’ll dive in and usually find we’re creating too many views or bindings for data that never changes. Removing that is quick and performance bounces back.

2 Likes

Is htmlbars still being actively developed? It looks quiet over at:

https://github.com/tildeio/htmlbars/commits/master

Yep. I’ve just moved over to Handlebars for a bit to make it ES6 module compatible because I hit some roadblocks when trying to use Handlebars in an ES6 project.

There will probably be another project on top of HTMLBars for just the binding stuff.

@krisselden I did notice helper syntax is now much nicer. I’m sure new people trying to learn Ember will appreciate it.

@wycats once HTMLBars drops do you plan on supporting both the Handlebars and HTMLBars projects or will HTMLBars completely replace it?

Yes, this is one of the primary features of 1.1

Handlebars is:

  • A lexer and parser
  • An AST
  • A compiler from the AST into an IR
  • A compiler from the IR into JavaScript

HTMLBars reuses the lexer, parser and AST, and replaces the compiler.

Emblem replaces the lexer and parser, but generates the shared AST and shares the compiler.

A nice thing about this is that since the AST is the shared representation, the Emblem parser will be able to use the HTMLBars compiler with no additional work :smile:

8 Likes

I’m curious to know if HTMLBars will use the same approach as RactiveJS. It even breaks text nodes in parts to update just what really changed.

For example, if we have <p>Hello, {{name}}</p>, it will break in:

<p>
"helo, "
"X"
"!"
</p>

to update just the second string, Although I’m not sure about the details, it could have its own drawbacks. But it seems a nice approach.

Edit: There is a gist, that I wasn’t aware of, relevant to the discussion: https://gist.github.com/wycats/5808149.

Will anything happen with this perf issue in the near term?

I agree with @eviltrout here.

Lest, I be misunderstood, I am in no way questioning the integrity of the benchmarks. It is interesting to see the performance difference. But rendering the same thing 10,000 times does not seem very real world use case. Given that ember is slower, the interesting question is why and what are the tradeoffs? Are we willing to make these tradeoffs for valid reasons?

But I personally think it would benefit everyone if you rendered a long collection of unique items. Perhaps a collection of spreadsheet like column data. And show how rendering that to the DOM performs.

Seems more real world and more likely to produce consistent GC behaviors.

The performance here is so different I am wondering if there is some unintended optimization in the Angular code. Not accusing, just curious, I don’t know enough about the internal implementation details, but @rupurt I think you hit the nail on the head with this statement:

We think Angular could optimize this by building a fragment of all items before inserting them.

Not claiming Ember would fair any better in a different benchmark. But could be a more interesting example.

Don’t know enough about Angular but I am curious about the difference here:

Ember

  renderPeople: function() {
    var numPeople = this.get('numPeople');
    var people = new Array(numPeople);

    for (var i=0; i < numPeople; i++) {
      people[i] = { name: 'Kanye West' };
    }
    this.pushObjects(people);
  }

Angular

  $scope.renderPeople = function() {
    var numPeople = $scope.numPeople;
    var people = new Array(numPeople);

    for (var i=0; i < numPeople; i++) {
      people[i] = { name: 'Kanye West' };
    }
    $scope.people = people;
  };

What is going on within $scope? I like that you reference appendChild vs documentFragment vs innerHTML. Interesting results.

What I would love to see.

1.) Build a large collection of unique items (some array or hash, well this is javascript we are talking about isn’t everything a hash? ). Build this outside the framework. Basic global object attached to window. Or else in a self enclosed function, or stand alone JS library.

2.) Render the collection to DOM with a variety of techniques (angular, ember, backbone, and some naive JS non framework way document.write, appendChild, etc), Bonus points if you can arbitrarily manipulate the garbage collection behavior.

3.) Then mutate, sort and filter the collection, these are very real world needs, and probably where performance matters most. I suspect this is where computed arrays and some of the ember magic will start to shine. But perhaps not. Would be interesting to know.

4.) Publish the spec and test source code to github for everyone to download, look at, play with and analyze implementation details.

@eviltrout is absolutely right when he says you can’t just render a large collection without thinking about it. I think we could all benefit from seeing the benchmark and understanding specifically what impacts performance. Somethings are definitely the responsibility of the frameworks, somethings are up to the developers tuning the app.

Solving the problem is going to be very specific to the application’s requirements and use case.

Anyway, not flaming at all. Thanks for publishing this. Very interesting stuff. Curious to learn more.

Also, @wycats @krisselden I didn’t realize HTMLbars was slotted for 1.1 timeframe that is awesome. Learn something new everyday.

1 Like

As of writing this the biggest issue with ember currently (for me) is rendering performance. I too have tried using List View, but it still lacks a lot of functionality to be usable for more than a select few scenarios.

I have two questions. Is there a roadmap for the next release of ember and does it address performance? :slight_smile:

@zeeraw, I concur. Ember has a lot of potential for becoming the #1 choice for the generation of web apps but this issue needs to be addressed somehow. I would be willing to help. We will launch soon.

The main issue for me is that when the cell gets moderately complex with lots of bindings and complicated DOM, there is noticable lag in rendering the list compared to other simpler lists even when the list is short.

I have tried group helper and it did help to a certain extent. Now it is working fine. But I think it could be much better without too much very involved optmization. The next step for me is to dig into the rendering and see where it is spinning cycles.

Interesting, when I tried the group helper with different bindings in each row, it did not work because clicking on any of the checkboxes always selected the first. I concluded that that’s the nature of the group helper and that it cannot be applied in the above scenario. Did I miss something?