Improving the performance of our JavaScript inheritance model
by Dylan Oudyk
When building the initial architecture for Netflix’s common TV UI in early 2009, we knew we wanted to use inheritance to share common functionality in our codebase. Since the first engineers working on the code were coming from the heavily Java-based Website, they preferred simulating a classical inheritance model: an easy way to extend objects and override functions, but also have the ability to invoke the overridden function. (Since then we’ve moved on to greener pastures, or should we say design patterns, but the classical inheritance model still lives on and remains useful within our codebase.) For example, every view in our UI inherits from a base view with common methods and properties, like show, hide, visible, and parent. Concrete views extend these methods while still retaining the base view behavior.
After searching around and considering a number of approaches, we landed on John Resig’s Simple JavaScript Inheritance. We really liked the syntactic sugar of this._super()
, which allows you to invoke the super-class function from within the overriding function.
Resig’s approach allowed us to write simple code like this:
var Human = Class.extend({
init: function (height, weight){
this.height = height;
this.weight = weight;
}
});
var Mutant = Human.extend({
init: function (height, weight, abilities){
this._super(height, weight);
this.abilities = abilities;
}
});
Not so super, after all
While his approach was sugary sweet and simple, we’d always been leery of a few aspects, especially considering our performance- and memory-constrained environment on TV devices:
- When extending an object, it loops over the prototype to find functions that use
_super
- To find
_super
it decompiles a function into a string and tests that string against a regular expression. - Any function using
_super
is then wrapped in a closure to achieve the super-class chaining.
All that sugar was slowing us down. We found that the _super
implementation was about 70% slower in executing overridden functions than a vanilla approach of calling the super-class function directly. The overhead of having to invoke the closure then invoke the overriding function which then invokes the inherited function yields a performance penalty that starts to add up directly onto the execution stack. Our application not only runs on beefy game consoles like the PS3, PS4 and Xbox 360, but also on single-core Blu-ray players with 600 MHz ARM processors. A great user experience demands that the code run as fast as possible for the UI to remain responsive.
Not only was _super
slowing us down, it was gobbling up memory. We have an automation suite that stress tests the UI by putting it through its paces: browsing the entire grid of content, conducting multiple playbacks, searching, etc. We run it against our builds to ensure our memory footprint remains relatively stable and to catch any memory leaks that might accidentally get introduced. On a PS3, we profiled the memory usage with and without _super
. Our codebase had close to 720 uses of _super
, that consumed about 12.2 MB. 12.2 MB is a drop in the bucket in the desktop browser world, but we work in a very constrained environment where 12.2 MB represents a large drop in a small bucket.
Worse still, when we went to move off the now deprecated YUI Compressor, more aggressive JavaScript minifiers like UglifyJS and Google’s Closure Compiler would obfuscate _super
causing the regular expression test to fail and blow up our run-time execution.
We knew it was time to find a better way.
All your base are belong to us
We wanted to remove the performance and memory bottlenecks and also unblock ourselves from using a new minifier, but without having to rewrite significant portions of our large, existing codebase.
The _super
implementation basically uses the wrapping closure as a complex pointer with references to the overriding function and the inherited function:
If we could find a way to remove the middle man, we’d be able to have the best of both worlds.
We were able to leverage a lesser known feature in JavaScript, named function expressions, to cut out the expensive work that _super
had been doing.
Now when we’re extending an object, we loop over the prototype and add a base
property to every function. This base
property points to the inherited function.
for (name in source) {
value = source[name];
currentValue = receiver[name];
if (typeof value === 'function' && typeof currentValue === 'function' &&
value !== currentValue) {
value.base = currentValue;
}
}
We use a named function expression within the overriding function to invoke the inherited function.
var Human = Class.extend({
init: function (height, weight){
this.height = height;
this.weight = weight;
}
});
var Mutant = Human.extend({
init: function init(height, weight, abilities){
init.base.call(this, height, weight);
this.abilities = abilities;
}
});
var theWolverine = new Mutant('5ft 3in', 300, [
'adamantium skeleton',
'heals quickly',
'claws'
]);
(Please note, that if you need to support Internet explorer versions < 9 this may not be an option for you, but arguments.callee
will be available, more details at Named function expressions demystified).
Arguably, we lost a teeny bit of sugar but at significant savings. The base
approach is about 45% faster in executing an overridden method than _super
. Add to that a significant memory savings.
As can be seen from the graph above, after running our application for one minute the memory savings is close to about 12.2MB. We could pull the line back to the beginning and the memory savings would be even more because after one minute the application code has long been interpreted, and the classes have been created.
Conclusion
We believe we found a great way to invoke overridden methods with the named function expression approach. By replacing _super
, we saved RAM and CPU cycles. We’d rather save the RAM for gorgeous artwork and our streaming video buffer. The saved CPU cycles can be put to work on beautiful transitions. Overall a change that improves the experience for our users.
References:
- John Resig’s Simple JavaScript Inheritance System http://ejohn.org/blog/simple-javascript-inheritance/
- Invoking Overridden Methods Performance Tests: http://jsperf.com/fun-with-method-overrides/2
- Named Function Expressions: http://kangax.github.io/nfe/
Originally published at techblog.netflix.com on May 16, 2014.