I was trying to find places to optimise a large project, and realised that the longest running function was actually the __extends call, which prompted me to look at the current code.
Two things immediately jumped out at me with the current code:
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
The first problem was that the __() function was being created, when it might not be used (though chances of that are probably very small).
The Solution would be to move the check into an if/else. Related to this is that null doesn't have any properties, so the property copying should be in the "not null" section.
var __extends = (this && this.__extends) || function (d, b) {
if (b===null) {
d.prototype = Object.create(b);
} else {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
var __=function() { this.constructor = d; };
__.prototype = b.prototype;
d.prototype = new __();
}
};
The second problem is purely performance related with the property copying. for(var p in b) will iterate over the entire prototype tree, but if(b.hasOwnProperty(p)) will then skip any that aren't directly on the object/class itself. (see Enumerability and ownership of properties)
The solution would be to use Object.keys(b) and iterating an array, which will work only on the object/class itself (and not it's prototype tree), it also automatically skips the same keys we'd be skipping anyway - so taking most of what we're wanting to the browser / native code instead of JS.
Unlike for...in, Object.keys fails when called on null, undefined or any other non-Object, so this requires some extra code to help prevent that happening.
From here we see that Object.keys() is an ES5+ function.
Suggested code:
var __extends = (this && this.__extends) || function (d, b) {
if (b instanceof Object) {
Object.keys(b).forEach(function(p){
d[p] = b[p];
});
var __=function() { this.constructor = d; };
__.prototype = b.prototype;
d.prototype = new __();
} else {
d.prototype = Object.create(null);
}
};
It's very hard to get reliable timings for the code on Chrome Profiling, but on average this looks like roughly a 10% speedup at startup for my project (38 classes in a nested tree up to six deep). There is no major benefit to this, though it does make the code slightly "safer".
The ES5+ code could potentially cause different behaviour if a class tries to inherit from a non-class (such as a number or string), though I'm not sure that is possible to begin with..
I'm pretty sure moving the __() function into an if/else would make an unmeasurable difference. The conditional check there is just to cover the extraordinarily special case that someone writes a class like class Foo extends null {...} which is technically valid so must be supported. That's the _only_ time the __() function is wastefully created, but it must be like 0.001% of cases (I doubt you'd have any such cases in the code you're measuring, and personally I've never encountered or found a use for extends null).
So if you are registering a speed improvement, I'd guess it's all from the for..in changes you made.
It is - the if/else thing is as much because I'm also doing ePub3 work, where you try to be a bit more efficient with the garbage collector - but it just makes logical sense to not create something that's never going to get used ;-)
This part also seems odd...
[in] a large project [...] the longest running function was actually the __extends call
Given that __extends is called only once per class _declaration_ (and not every time a class instance is created), your large project must be spending a huge proportion of it's time creating new classes (not just instantiating them).
I can only imagine this would be via class expressions that are repeatedly executed on some hot path. If there was some way to avoid continual re-evaluation of class expressions (eg by moving them out to a normal module-level class declaration, perhaps with some extra constructor parameters), I imagine __extends would no longer be a hot function, and you'd probably get an even bigger speedup.
The cause of me looking at it isn't really relevant - was just the reporting method I was using and it grouping things together - hence why I saw the two minor issues in it (it took longer writing this issue out that changing and testing the code lol) - it's also why it's really hard to measure the speed difference - for something that's called only once per class it's the iterating that takes the longest time as nothing else is that expensive - and even that expense depends on how much is actually inside the class and tree (mine is pretty big, hence the 10%).
Saving 50ms is worth it for me, not sure it'd make any difference for 99% or more of the projects using TS, but better code is always worth talking about ;-)
it just makes logical sense to not create something that's never going to get used ;-)
Bear in mind that function declarations are _hoisted_, so just because you lexically nest it inside an if statement, doesn't necessarily mean the function isn't created anyway. I think the behaviour is not well-defined across all js engines. It's definitely an error in strict mode to nest a function declaration inside an if block. So it's probably best to avoid.
var __=function() { this.constructor = d; };
Got me there :)
You were completely right though, hence I changed the issue code :-P
Just a hypothetical....
If you had a project that creates 1,000,000 instances across 10 different classes, then __extends would be called only 10 times and the GC pressure of those 10 __() instances (say 9 of which are necessary anyway) would be utterly negligable, and even the tiniest tweak to the code that repeats 1 million times would yield far greater gains.
OTOH, if your project has __extends on a hot path, then its more like creating 1 instance each of 1,000,000 class expressions. That just seems a wildly inefficient approach to using classes, and again the gains would be far greater in factoring out the class expressions to reduce the number of __extends calls by several orders of magnitude.
Seconding @yortus 's comments that perf of __extends should not be measurable in any reasonable scenario. If this is your primary objective (rather than improved semantic correctness), the best thing to do would be to have your own __extends at global scope before the first TS code runs so that that _-extends takes precedence. Alternatively, you could run with --noEmitHelpers and write an explicit __extends somewhere in your own codebase.
The primary goal of this code is to be more "correct", the speedup is a side effect of that - the final in-memory classes used will be identical so it's purely a case of changing how it gets there slightly. I'd hate to see any code with a million classes in it!
One problem here is that we need the __extends code to work on ES3, so we can't use Object.create.
You might say "Easy fix, you can emit different __extends based on the --target" but this is a cure worse than the disease -- because __extends is found in the global scope, you could get into a state where a "more compatible" ES3-compiled TypeScript library loaded before an ES5 TypeScript user codebase would change the behavior of the user code! Or vice versa, if someone was somehow intentionally depending on the non-spec behavior of __extends.
Good point - though it would be a problem if an ES5 class was loaded before an ES3 one I think - but do ES6+ classes even get this included?
Having a slightly longer version that checks would fix it - but not sure that the extra code would be worth it:
var __extends = (this && this.__extends) || function (d, b) {
if (b instanceof Object) {
if (Object.keys) {
Object.keys(b).forEach(function(p){
d[p] = b[p];
});
} else {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
}
var __=function() { this.constructor = d; };
__.prototype = b.prototype;
d.prototype = new __();
} else {
d.prototype = Object.create(null);
}
};
interface Object {
// Type system in typescript is too weak.
assign( target : any , ...sources : any[] ) : any
}
/// Inheritence with static constructor support
var __extends = ( Sub , Sup ) => {
/// Inherit static properties
Object.assign( Sub , Sup )
/// Create inherited prototype without parent constructor calling
Sub.prototype = Object.create( Sup.prototype , {
constructor : {
configurable : true ,
writable : true ,
value : Sub ,
}
} )
/// Call static constructor
if( Sub.initializer ) Sub.initializer()
}
You can boldly include polyfills for Object.assign and Object.create because it is standard.
Most helpful comment
Seconding @yortus 's comments that perf of
__extendsshould not be measurable in any reasonable scenario. If this is your primary objective (rather than improved semantic correctness), the best thing to do would be to have your own__extendsat global scope before the first TS code runs so that that_-extendstakes precedence. Alternatively, you could run with--noEmitHelpersand write an explicit__extendssomewhere in your own codebase.