How fast can a SproutCore app load?

Checkout this simple todo app running on Google AppEngine. This is a work-in-progress sample app I’m working on to show at the next SproutCore Founders Meetup. The source code includes the SproutCore, a Google AppEngine backend and a Merb backend, with more to follow.

SproutCore applications are thick-clients, which means they download once to your web browser and run independently of the server. A lot of people naturally think this means that your server can run slow and it won’t matter. In some cases, this is true. More often, though, your application server actually needs to respond faster than it did before.

When you write a server-driven web app, the user constantly has to wait for the server to respond with some new HTML or Ajax between every few clicks. It’s slow and boring but at least it is consistently slow and boring.

When you switch to a SproutCore app, things speed up considerably for your users. Once your app is loaded, they can fly through the interface, selecting items, moving them around, deleting, creating, etc. The only time they have to wait is…you guessed it…when your server has to get involved.

Now that slow server of yours that was mere annoying before sticks out like a sore thumb.

Thankfully, SproutCore apps also free you to make your server side much simpler. All it needs to do is perform some basic data manipulation and return json as quickly as possible. This is often far less work than you had to do before server side, so you don’t have any excuse.

Make your SproutCore app beautiful and rich…and then make your server fast so won’t ruin that great experience.

SproutCore 1.0: New Bindings and Observers

Property observers and bindings form the very core of SproutCore’s API.  In fact, SproutCore is one of the few frameworks to have implemented bindings and observers from the very beginning instead of bolting it on at a later date.  This contributes heavily to SproutCore’s compact design.

The centrality of bindings and observers also makes it one of our most important so that is why it was one of the first things we revisited for SproutCore 1.0.  Since this code as so old, it was quite easy to rewrite it using some more efficient techniques, giving us a 2x performance boost when creating new bindings and setting up new observers.

In addition to a raw perf boost, we also changed some key behaviors related to timing that could change how your application behaves.  These changes should speed up your app even further by reducing the number of bindings that fire in your application.  Depending on how you’ve written your code, however, it could change how your code behaves.  Here is what we did:

Change 1: setIfChanged Becomes Default

Today, every time you call set() on an object, SproutCore will invoke any observers you have as well, even if the value you are setting has not changed.  For example, if you write some code like this:

obj = SC.Object.create({
  firstName: null,

  firstNameObserver: function() {
    console.log("observer fired");
  }.observes('firstName')
});

obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired

Now, the default behavior has changed.  With the new code, observers will only fire if the value you set actually changes.  For example, the output of the above code with SproutCore 1.0 is:

obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
obj.set('firstName', 'John');

This means you can write less code to make sure your observer is not fired more often than necessary.  Big win.

The only problem with this change is that this means computed properties may sometimes not be set more than once if you call set() with the same value.  Usually this will not matter, but if your computed property needs to be called everytime you try to set the value, you can change this behavior by adding the indempotent() helper to your property:

fullName: function(key, value) {
  // do something interesting here
}.property().indempotent(NO)

Adding this qualifier to your property will cause it to be called every time there is a set() instead of just when the value changes.

Change 2: Binding and Observer Timings

Currently, whenever a property changes on your application, observers are notified immediately unless you are already inside another observer handler.  Then they are queued and notified when the root observer exits.  Since bindings are implemented using observers, bindings will trigger when your other observers finish but before the set() returns, again unless you are more than one level deep in observers.

If this sounds confusing to you, that’s because it is confusing.  It also makes it hard to predict exactly how change notifications will propagate through your application.

With SproutCore 1.0, this timing behavior is significantly cleaned up.  The rules are simple:

  • Observers (i.e. methods you setup with observes() or addObserver()/removeObsevers()) will fire immediately when you call set(), unless wrapped inside of a beginPropertyChanges()/endPropertyChanges() pair.  In that case, they will fire immediately when you call endPropertyChanges().
  • Bindings on the other hand queue up.  They fire only once at the end of the run loop, when all of your normal event handling or timer code finishes executing.  Bindings are also coalesced.  If a binding changes more than once during a single run loop, it will still only fire once at the end.

The net effect of this timing change will be that when a property changes on an object, the observers you have written for that object will execute immediately so you can update other state within your object.  But, the bindings that tie together the objects in your app will fire all at once.  This will cause changes to relay through your app rather like a wave, starting at the model or view layer and propagating to the other side in layers.

This more controlled rate of change will generally not require you to change any code, but it does mean that code you put in to gate changes and control duplicate messages will be less necessary.  It will also generally speed up your application since binding change notifications occur less frequently.

Change 3: Cache Computed Properties

I mentioned this the other day so I won’t spend a lot of time on it now, but the basic idea here is that often the return value of a computed property only changes when one of its dependent keys or some other state changes.  In the mean time, multiple calls to get that property could return a cached value.  Caching used to require many lines of code, but now it can be done with just one.  Here is how you write a cached property:

contact = SC.Object.create({
  firstName: 'John',
  lastName: 'Doe',

  // computed property, cached until first or last name changes
  fullName: function() {
    console.log('computing fullName') ;
    return this.getEach('firstName','lastName').compact().join(' ');
  }.property('firstName', 'lastName').cacheable()
});

contact.get('fullName')
> computing fullName
> "John Doe"

contact.get('fullName')
> "John Doe"

contact.set('firstName', 'Jane');
contact.get('fullName');
> computing fullName
> "Jane Doe"

contact.get('fullName');
> "Jane Doe"

Now the fullName computed property will be called only once and cached.  You can get() as often as you like without paying the cost to recompute the value.  If you ever need to recache a computed property without changing one of its dependent keys, you can do so by calling properyDidChange().

Change 4: Use a target and method with addObserver() and removeObserver()

The low-level addObserver() and removeObserver() methods are used to setup and remove observers on an object.  Their API currently requires you to pass the property you want to observe along with a function to execute.  To execute the function in the context of a particular object, you had to wrap you function using Function.bind(), which adds a stack frame, another object and closure and generally slows everything down.

Now addObserver() and removeObserver() will be able to accept a target and method, which will be tracked separately for you.  This allows you (and SproutCore) to avoid having to create these extra closures, which increases performance and helps on limited JavaScript engines like IE7’s JScript.

Change 5: Disable Automatic Observer Notification

Normally, when you call set() with a new value on a property, SproutCore will automatically notify observers when the property has changed.  Sometimes, however, you may have a property with a more complex behavior and you don’t want the automatic notification process to take place.  Now you can override this behavior by implementing a new method on your class called automaticallyNotifiesObserversFor(key).  Returning NO for this method will disable automatic notification for the passed key.

This technique is most useful when used with computed properties.  For example, let’s say you decide to implement a computed property that only allows values greater than 10 to be set on it.  Here is how you might implement this:

obj = SC.Object.create({
  // computed property can be set to any number
  // as long as it is greater than 10
  value: function(key, value) {
    if ((value !== undefined) && (value > 10) && (value !== this._value)) {
      this.propertyWillChange('value'); // implement notification manually
      this._value = value ;
      this.propertyDidChange('value', value) ;
    }
    return this._value ;
  },

  // disable automatic notification for this property
  automaticallyNotifiesObserversForKey: function(key) {
    return (key === "value") ? NO : YES ;
  },

  // add observer just to show  what happens
  valueDidChange: function() {
    console.log("value did change to: %@".fmt(this.get('value')) ;
  }.observes('value')
});

obj.set('value', 20) ;
> value did change to 20

obj.set('value', 5);
[no output]

obj.set('value', 21)
> value did change to 21

obj.set('value', 21)
[no output]

This is definitely an advanced topic, but if you need to control precisely how and when property change notifications happen, it can be a god-send.

In general, you will be able to use the new property observers and bindings in SproutCore without having to worry about the changes here.  You should just notice your app running a little faster.  Understanding how some of these work though can dramatically reduce the amount of code you have to write in certain advanced situations.  If you have questions about this once its available, be sure to ask on IRC or the mailing list!