How Is Adding A New Class With Prototype Methods A Form Of V8 Optimization In Js?
Solution 1:
Well, that's an interesting one.
Foreword
Without any details on that "well known" "increase [on] performance" we can only speculate what was meant.
History of the logger
When I first saw your question and the code I realized the code comment must be outdated.
classDerivedLoggerextendsLogger {
/**
* Create a new class derived logger for which the levels can be attached to
* the prototype of. This is a V8 optimization that is well know to increase
* performance of prototype functions.
* @param {!Object} options - Options for the created logger.
*/constructor(options) {
super(options);
this._setupLevels();
}
// ...
}
module.exports = (opts = { levels: config.npm.levels }) => (
newDerivedLogger(opts)
);
It was wrong in one way, since, via the _setupLevels()
call in the constructor, the methods are defined on the instance, not on the prototype. See here or here for details on that topic.
So I dug through the history to find the first occurence of that comment with unchanged code ...
This is what the original code looked like when the above comment was added:
// Create a new instance of a winston Logger. Creates a new// prototype for each instance.module.exports = function (opts) {
// ...//// Create a new prototypal derived logger for which the levels// can be attached to the prototype of. This is a V8 optimization// that is well know to increase performance of prototype functions.//functionDerivedLogger(options) { Logger.call(this, options); }
util.inherits(DerivedLogger, Logger);
// ...DerivedLogger.prototype[level] = function (msg) {
The current code changed in another way: the DerivedLogger
is no longer created with every logger instance but only once, when the module is loaded.
Analysis
Until here I did not realize that the Winston authors created new prototypes in the logger's create function:
// Create a new instance of a winston Logger. Creates a new// prototype for each instance.//module.exports = function (opts) {
So, when a new logger is to be created, not only an instance is made, but a whole new prototype is created too.
[Logger] (A)
^
|
+---------+--------+
| |
[DerivedLogger #1][DerivedLogger #2] (B)
| |
logger#1logger#2
Derived loggers are not reused.
Conclusion
The original intent definitively was to prevent modification/pollution of Logger
(A) whenever a new logger instance is created.
Though creating logger methods on a prototype to prevent wasting memory in repeated instance methods (see the linked questions at the beginning) seems to be thwarted by the repeated creation of new prototypes.
I even believe the performance gained by creating a prototype holding log methods over defining them directly for the instance is swallowed by the creation of the prototype object.
However, I'm not 100% confident that the discussed interpretation was the original intent and am open for corrections and clarifications.
Bonus
(I found this during my research and probably unrelated to the above Winston code.)
Since it bothered me that the original author claimed defining methods on the prototype would optimize things for V8, I started searching for updates on this topic and I found an article by V8 developer Mathias Bynens: JavaScript engine fundamentals: optimizing prototypes.
He's discussing how most Javascript engines (not only V8!) internally store objects and how they handle property access. You might also want to read another article by him on Shape objects.
I won't fully recap that in detail here, though there seems to be a unique detail to V8 how accesses along the prototype chain is handled:
V8 treats prototype shapes specially for this purpose. Each prototype has a unique shape that is not shared with any other objects (specifically not with other prototypes), and each of these prototype shapes has a special
ValidityCell
associated with it. ThisValidityCell
is invalidated whenever someone changes the associated prototype or any prototype above it. [...] The next time the Inline Cache is hit, the engine has to check the shape of the instance and theValidityCell
. If it’s still valid, the engine can reach out directly to the Offset on the Prototype, skipping the additional lookups.
(Bold text by me.)
So, unique to V8 seems to be the fact, that they track whether or not the prototype is still "in shape". This allows V8 to reduce checks involved in the prototype chain processing.
Post a Comment for "How Is Adding A New Class With Prototype Methods A Form Of V8 Optimization In Js?"