Skip to content

Instantly share code, notes, and snippets.

@fivetanley
Last active August 29, 2015 14:22
Show Gist options
  • Save fivetanley/2db58ffd75f96131f528 to your computer and use it in GitHub Desktop.
Save fivetanley/2db58ffd75f96131f528 to your computer and use it in GitHub Desktop.
Skylight Email Excerpt June 6, 2015

(Originally taken from the Skylight "almost daily" emails, written by Tom Dale)

It was a very busy week. We spent the first three days of the week digging deep into Glimmer performance. As we've been scrambling to bring the benefits of this brand new approach to rendering to the many existing Ember apps, we inadvertently added some performance regressions while adding backwards compatibility features.

We spent many hours staring at the profiler in Chrome, trying to figure out how to optimize Glimmer's rendering performance at both the micro and macro level.

One of the biggest improvements was minimizing the number of DOM manipulations for certain cases of re-rendering an each helper in Ember. You can see more of the specific details in the commit here: https://github.com/tildeio/htmlbars/commit/64be35ef03953d80cfe4434446a01dd483bba7b0

This definitely involved busting out some rusty Computer Science skills, but it's always fun to explore algorithms and data structures in high-impact ways. This case is interesting because you are trying to reflect changes from one array onto another array, but mutating the target (DOM) array can be an order of magnitude slower than the input. That makes the normally obvious algorithmic choices less obvious.

We also spent a lot of time ensuring that we were writing our code in a way that allowed JavaScript VMs like V8 to run it as fast as possible.

Under the hood, V8 is written in C++, and uses something called a just-in-time compiler (frequently referred to as a JIT) to turn JavaScript code into native code while you're running it, allowing it to operate much, much faster.

The JIT, however, relies on the fact that, while JavaScript is a very dynamic language, many usage patterns of JavaScript are not dynamic. If it can detect those cases, it can compile JavaScript down into something that looks more like low-level languages like C or C++.

For example, in a low-level language, variables are always a specific type. In C, I can declare a variable like this:

int count;

Once I declare this variable as an integer, it will always be an integer.

In JavaScript, however, variables have no type information. I can declare a variable and assign it to a number, then a boolean, then a string, and then an object; this is all totally legal.

However, if I declare a variable and only assign integers to it, the JIT detects that my JavaScript is acting very "C-like" and will compile it into C-like low-level code, giving me a significant performance boost.

This is great, but the VM is always on the lookout for me doing something that violates those assumptions. In those cases, to prevent your code from doing something incorrect, it removes the optimization and falls back to a slow path. We call this a deoptimization, and they can cause a huge performance impact on your application, even though you're writing totally legal JavaScript.

One of the most important aspects of writing fast JavaScript code is preserving the "shape" of objects you create. That is, when you create an object, you should always (when possible) assign it the same properties in the same order, even if that particular instance doesn't need all of the properties you assign. Under the hood, this tells V8 that it can represent that object as a struct, a data structure in C that has a fixed size and fields that is very fast.

This may all seem very abstract, so I recommend taking a look at this real commit from HTMLBars where we make a few small tweaks to an object in the system, AttrMorph, that lead to non-trivial performance improvements: https://github.com/tildeio/htmlbars/commit/98b625f1cbe378451fd5f42313d435ab48e45f27

How do you diagnose deoptimizations? The first thing is to take a profile of your app; you really don't want to spend time optimizing paths that have a microscopic impact on real-world performance.

Once you have your profile, look for a small, yellow triangle that tells you which functions have been deoptimized. If you hover your mouse over it, it will often tell you the reason it was deoptimized.

image showing chrome profiler

This is a great first step in helping you identify problem areas, but doesn't tell the full story. If you are feeling brave, you can do what we did: dive deep into the guts of V8.

It's a little bit complicated to get up and running the first time, but there's no better way to understand the performance of your code in V8 (other than becoming a VM hacker yourself, maybe).

We use and recommend IRHydra (there are two versions, make sure you get v2), a tool from V8 maintainer Vyacheslav Egorov aka Mr. Aleph.

IRHydra allows you to put Chrome into an extremely detailed debug mode that logs the actual bytecode generated by V8's JIT, then load those logs into a graphical UI that help you explore, line by line, where deoptimizations are happening.

Again, it's a little overwhelming, but it's the most powerful tool I know of for getting deep into microptimizing your JavaScript code.

After all of that performance work, we switched gears mid-week to work on a new feature we're excited to get into Ember.js, hopefully for the 2.1 release. That feature is angle bracket components, and they offer not only better performance than traditional components, but have more feature too.

Here's the TL;DR of angle bracket components: rather than invoking them with curlies, you can invoke them with angle brackets, like any other HTML element ().

One of the major advantages is that it significantly reduces the learning curve for new users. Binding attributes to components is exactly the same as binding attributes to normal HTML elements.

This week, we were focused on the trickiest feature to implement for these new components: the ability to define their root element inside the template.

Components are typically backed by an element, by default a

. When you use a component in your Ember template, you'll see a
with the ember-view class name when you open up the web developer tools and look at the DOM. Here's an example component from Skylight after Ember has put it into the DOM: it's just a plain old
with some extra attributes added.

image showing plain div with classes instead of custom elements

While the default element for components is a div, you can customize what kind of element is generated by setting a component's tagName property in JavaScript. You can also customize things like the class, ID, and any other attribute with properties like classNames, elementId, attributeBindings, etc.

However, what's annoying about this is that you have to do this all in JavaScript, when we already have something that's custom-made for describing HTML and how data binds to it: Handlebars!

With the new angle bracket components, you can describe everything about how the component's root element is generated simply by describing it in the component's template.

For example, if you want the component to be backed by a

instead of a
, with the new feature, you can just write a template like this:

<table>
  ❴❴#each rows as |row|}}
    <tr>
      <td></td>
      <td></td>
    </tr>
  /each}}
<table>

Ember will detect that the template has a single root element and make that the element, instead of the default

.

This gets one step cooler by also allowing you to specify custom element names. For example, imagine we have the time-explorer template. We can write our template like this:

<time-explorer>
 <!-- sick D3 action goes here -->
<time-explorer>

Now, when we look at the DOM in our browser, we don't see a

but an extremely semantic, custom made element: the element!

image showing custom elements

This is awesome because it allows you to extend the web platform today, bending HTML to your will and adding custom elements purpose-built for your app. World domination will be yours in no time!

If you want to see how this beautiful custom element sausage gets made, check out the PR here: emberjs/ember.js#11359

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment