0

Then, I suppose, the fuller question is, "How are those considerations affected when the Javascript engine is known (on a per popular engine basis)."

The reason I feel I need to ask "community" is because those like Strongloop who create things like Node.js, also write code like util.inherits, a function inside which, instead of: TheConstructorFunction.prototype.constructor = TheConstructorFunction they create a property definition which itself has 4 properties being set:

ctor.prototype = Object.create(superCtor.prototype, {
    constructor: {
        value: ctor,
        enumerable: false,
        writable: true,
        configurable: true
    }
});

This seems inefficient to me, but then, I've only been striving to write better Javascript for 3 years as opposed to authors of code like this. Is there an explanation for why this is or isn't efficient?

One answer says that util.inherits is not inefficient because subclass operations are rare. But considering some libraries deal with hundreds and thousands of subclass operations happening between build to test result observation, if Node.js internally uses util.inherits then that affects development time of its consumers. I think, on this level efficiency does matter and I hope util.inherits is not being used internally. I don't even understand the purpose of util.inherits. Why have it at all? I think it promotes compounding inefficiency. require('util').inherits(C1,C2); is almost synonymous and likely slower than C1.prototype=Object.create(C2.prototype); C1.prototype.constructor=C1; If it is just meant to lure consumers, I'm cool with that. My concern is if these pros are using that function internally since larger libraries will be depending on Node.js' efficiency. As for making .constructor non-enumerable... I think the case where its "enumerability" matters is rare and should be left up to the consumer, but if internal operations are depending on util.inherits, this isn't really being left up to the consumer.

flcoder
  • 713
  • 4
  • 14
  • The example you gave of `ctor.prototype` is not the same as the `prototype.constructor` you compared it to. That's apples to oranges. – TylerY86 Sep 29 '16 at 19:51
  • "*some libraries deal with hundreds and thousands of subclass operations happening between build to test result observation*" - oh really? What makes you think that? And no, even this scale hardly matters, compared e.g. to reading the source files from the disk. "*almost synonymous and likely slower*" - neither, actually. If you are that curious, benchmark them yourself. And no, standard library functions do not promote inefficiency, they promote *simplicity*, which saves developers much more time than microoptimisations ever could. – Bergi Sep 29 '16 at 20:17
  • @Bergi, you might be right, and I accept your challenge. I will benchmark them at some point in the near future. Because, if you're right, then I am just wasting more time thinking about any of it. But if you're wrong, then you and a whole slew of bloggers/educators/professionals are spreading a fairly egregious concept. I wrote a PHP windfarm management SaaS and it utilizes nearly 20-50 different classes depending on the type of request, I'm considering implementing the system in Node.js but I plan to have an underlying system similar to Wordpress as its engine.... so there's one example. – flcoder Sep 29 '16 at 20:44

2 Answers2

1

This seems inefficient to me

No. Efficiency does not matter here. Subclassing is a rare operation, and even if the code would be slower than the simple assignment (there most likely isn't much difference) this does not affect performance.

What matters, especially for library functions like utils.inherit that are used everywhere, is correctness. So why do they use property attributes? To make the .constructor non-enumerable, like it is standard for .prototype objects that are natively created and therefore generally expected.

Given that neither speed nor memory need to be considered here, optimisations as you seem to expect them do not take place here.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I updated my answer because there's not enough room to address your conclusion here. But think about what you're saying... which I'm reading as: "Neither speed nor memory NEED TO BE CONSIDERED for library functions like utils.inherits that are USED EVERYWHERE" What matters most is "correctness". I think correctness is important, sure. Well, it better be, because if it's not then it's broken. But this is my problem; I see a lot of (too much) justification for allowing sub-optimal code. – flcoder Sep 29 '16 at 20:16
  • @flcoder correct code is optimal code :-) My argument is specifically about `util.inherits`, other functions that are actually performance-critical (e.g. Node's `Stream` implementation) *are* optimised to the last detail, however without sacrificing correctness even then. – Bergi Sep 29 '16 at 20:22
  • I get what you're saying... because I've been hearing it all over the place... and I'm guessing that's what professors are teaching; that it's ok to bloat when you get the feeling that a certain piece of code does not need to be concerned with optimization. Firstly, I think util.inherits is just completely useless, and secondly, I agree, in your words that it is "used everywhere" and that is what I find disconcerting. This is how "the pros" roll... :P And that's why I asked my question to a whole community. I want to see what sources others consider reliable Javascript optimization advice. – flcoder Sep 29 '16 at 20:35
  • I don't see any bloat in the implementation… it can't be made much shorter while maintaining correctness. "*I think util.inherits is just completely useless*" - it might be with ES6 classes, but it did help before. Just like all util library function, it provides a simple and reliable way to do a menial task. Don't you think there's a reason why it's used that often? Regarding "*reliable Javascript optimization advice*", you will *always* find "avoid [premature optimisation](http://en.wikipedia.org/wiki/Premature_optimisation)" as the first rule. `util.inherits` is a very good example of that. – Bergi Sep 29 '16 at 20:44
  • @Bergi There's a difference between being pendantic for correctness sake and prematurely optimizing. The behavior around inheritance can adversely affect compiler performance. Having worked with bluebird you had to have read [this](https://github.com/petkaantonov/bluebird/wiki/Optimization-killers). The mere use of `__proto__` would disable optimization. Specifically speaking, optimization targets correctness, so that you don't have to be optimal, just correct. – TylerY86 Sep 29 '16 at 21:06
  • @TylerY86 Sure there's a difference, I just think that the OPs suggested replacement for `util.inherits` both forfeits correctness and is premature :-) Yes, I'm familiar with the linked document, though it is meant for library authors (and bluebird contributors) not bluebird users. And while it's useful, you always have to consider where exactly which usage of a thing like `__proto__` prevents which optimisations. – Bergi Sep 29 '16 at 21:21
  • @Bergi The correct implementation could have improved other user's experience, however it was implemented incorrectly (and in effect, suboptimally) until [this revision](https://github.com/nodejs/node/commit/29da8cf8d7ab8f66b9091ab22664067d4468461e) added a test case and corrected it. I honestly think that using `util.inherit` at all is a bad pattern to start with. Although I admit I'm not sure if the OP's example is identical to what inherit attempts to accomplish, I called it apples to oranges. We'd need a full [MVCE](http://stackoverflow.com/help/mcve) to be sure though. – TylerY86 Sep 29 '16 at 21:26
  • @TylerY86 Well, it was coded correctly until the advent of ES6 (that introduced `Object.setPrototypeOf`). The usage of `util.inherits` together with ES6 `class`es seems to be an antipattern indeed, one rather should use `extends`. I wonder why they "fixed" this instead of throwing an error message or a warning on how to do it better. – Bergi Sep 29 '16 at 21:32
  • @Bergi The problem was Node & V8 in 2010. `NOTE: If this file is to be loaded during bootstrapping this function needs to be revritten [sic] using some native functions as prototype setup using normal JavaScript does not work as expected during bootstrapping (see mirror.js in r114903).` – TylerY86 Sep 29 '16 at 21:46
  • (Woops, forgot to add;) It wasn't technically correctly coded from the beginning. Mostly because it couldn't be. lol – TylerY86 Sep 29 '16 at 21:53
  • Instead of moving to a new chat, you could [continue the discussion here instead](http://chat.stackoverflow.com/rooms/124590/discussion-between-flcoder-and-tylery86). – TylerY86 Sep 29 '16 at 22:00
  • The difference between my example and `util.inherits` method is that `inherits` makes instances' `.constructor` property non-enumerable. And whether that is "correct" or not depends on what the class instances are going to be used for. When they aren't going to be used as iterables, then it is bloat, which is not my idea of "correct". When you're debugging an instance object, maybe you WANT to see or know that there is a constructor property and that's just one case. But if all Node.js's instances come from a result of `util.inherits`, it's not very cool at all, or correct, or convenient. – flcoder Sep 29 '16 at 22:02
  • @flcoder It's correct because it's the standard behaviour of `.constructor` properties on all `.prototype` objects even when they don't inherit from something special. The attribute it simply preserved to be what it was before `util.inherits` was called. – Bergi Sep 29 '16 at 22:06
  • @Bergi Dropping a bomb for discussion. Is the entirety of `util.js` method `inherits` just a maintained artifact of premature optimization in an attempt to improve member inheritance resolution? Look at the [early code](https://github.com/nodejs/node/blob/dd53ceebe4e87ed2c71486e17cdcd33af88cec59/lib/util.js) where the `superCtor.prototype` derivative is immutable. – TylerY86 Sep 29 '16 at 22:14
  • @Bergi your reasoning is not entirely illogical. All Javascript's built-in objects' property prototype's property `constructor` is non-enumerable. But that doesn't COST anything, they are BUILT IN. When the engine hands over the context, that's when you may want to start considering whether or not you REALLY need to create propertyDescriptors, .i.e., new objects. You may decide there is some significant benefit to the cost of making a property non-enumerable for some prototype objects, but you may decide, no, there is no significant benefit, let's NOT pay the price of new object creation. – flcoder Sep 29 '16 at 22:30
  • @TylerY86 [This](https://github.com/nodejs/node/commit/1ba2c3213526ccacac587dd10362626cec862d36) is interesting indeed. I can only guess that omitting the descriptors that did default to `false` was a bug. – Bergi Sep 29 '16 at 23:07
  • The addition of the other attributes was *to increase performance* according to that log entry, so they weren't concerned with side effects as bugs... lol – TylerY86 Sep 29 '16 at 23:12
  • @flcoder If you see a "COST" in the creation of the object for the property descriptor that is laughable. This is so insignificant. If I recall correctly, there's even an optimisation in V8 that prevents these literals that are passed into `create`/`defineProperty`/`defineProperties` from being instantiated at all. The "price" to pay here is clearly outweighted by being able to stick to the standard. – Bergi Sep 29 '16 at 23:13
  • @TylerY86 I presume that the bug was discovered because it affected performance (having unconfigurable properties on an object prevents optimisations of the memory-representation), not that they initially intended to make the `constructor` unwritable. But I can only guess here, you can dig around (or best ask the authors) if you want to investigate any further. – Bergi Sep 29 '16 at 23:18
  • @Bergi perhaps they didn't always default to false and only became a bug once that behavior changed so they had to go in and modify it. But see... none of that would even be necessary if you didn't even bother to pay the cost of creating a new object just to have a descriptor that behaves exactly like built-in object.prototype.constructor descriptors behave. It's a waste of human resources/money/time/cycles. Just think about how many modules are out there right now calling require('util').inherits() like it's cool... wasting cycles. – flcoder Sep 29 '16 at 23:21
  • CPU cycles are cheap (as well as requiring a cached module or calling an inlineable function). Discussions like this are not. Factoring out repeating code into an `inherits` function and providing it as part of a library was a decision made by experienced developers, and has probably saved hours of developer time elsewhere. – Bergi Sep 29 '16 at 23:32
  • Girlscout cookies are cheap too. But if you say that every time one comes knocking at your door, you will eventually not be able to pay for your house. Then, your wife is going to reconsider why she married you. – flcoder Sep 29 '16 at 23:44
0

This is best answered by the authors of the JavaScript engines.

It's a process called performance tuning.

Here's Google's guide. Here's Mozilla's guide.

Making Node.js run javascript fast is the core of Strongloop's business. They take javascript optimization seriously.

There's a few gotchas like this.

Mostly though, don't block the event loop - use promises, async operations, callbacks, etc.

Benchmark your code with reasonable realistic test cases. Make sure to check coverage.

Once you get down to the main cause of your search for performance tuning, you have to start digging deeper. You have to profile your code.

Thorsten Lorenz has an aging but still mostly relevant GitHub repository that contains a lot of useful insider V8 info.

There's lots of developers that post blog articles, like this guy. You have to make sure they know what they're talking about. He does.

Bluebird, a notoriously well performing implementation of Promises, is a pinnacle of correct optimization. They have an article and a guide on things that they've noted that are simply common things that kill optimization.

Update

The example you gave of the util.s implementation an inheritance chain should probably be a separate question. I'm not sure if they're monkey patching there, I'd have to see the rest of the class and prototypes.

See this MDN article for the pros and cons and variations of known ways to implement inheritance, specifically for the use of Object.create, but the others as well.

The only good reason for extending a built-in prototype (aka "monkey patching") is to backport the features of newer JavaScript engines; for example Array.forEach, etc.

Here's some reasoning on why this kind of thing is done. I answered a question not too long ago about injecting a class into the top of an inheritance chain for the purpose of adding an extension method to all of their children. It's not a great idea.

Turns out, Object.create in the util.js implementation of inherits may have been an attempt at optimization originally. According to early revisions, it made the prototype implicitly immutable, theoretically improving method resolution performance. Pedantically speaking, it was never correct, and has always been essentially a hack around the prototypal inheritance capabilities of JavaScript. New revisions replace this with Object.setPrototypeOf.

Edit: If you down-voted this, I would appreciate a glimpse of why.

Community
  • 1
  • 1
TylerY86
  • 3,737
  • 16
  • 29
  • I'll build on it if I find any more significant general details. Performance tuning can go really in depth. [Check out this question](http://stackoverflow.com/questions/39576177/locate-corresponding-js-source-of-code-which-is-not-optimized-by-v8/39762720#39762720). – TylerY86 Sep 29 '16 at 17:47
  • I edited my question and scoped `prototype.constructor` to `TheConstructorFunction.prototype.constructor`... which is the same as their `ctor.prototype` constructor property definition. I get that they do this to make it non-enumerable, but I don't get why. If, I'm making a Collection type class, ok... maybe then I'll go ahead and make certain properties non-enumerable. But for any other type of class I'm not sure it's worth the extra trouble of "hiding" the constructor property. I just see this kind of bloat everywhere which makes me wonder if even the pros know what they're doing. – flcoder Sep 29 '16 at 20:10
  • Do you have a particular file that you're referencing I can look at? Ah right, it's [`util.inherits` in util.js](https://github.com/nodejs/node/blob/master/lib/util.js#L958)... – TylerY86 Sep 29 '16 at 20:13
  • Does [this revision](https://github.com/nodejs/node/commit/29da8cf8d7ab8f66b9091ab22664067d4468461e) (gg. Michaël Zasso) where they get rid of `Object.create` make you happier? :) – TylerY86 Sep 29 '16 at 20:17
  • Hahaha, I don't know... Now I have to go and research V8's implementation of E6's Object::setPrototypeOf function. My whole point is: SERIOUSLY util.inherits - just, WHY? It's like they're just trying to avoid doing work that matters. Let's see how many modules we can produce and require, because it's so convenient. It is fairly convenient to throw efficiency out the window, I suppose. I just don't get it, Javascript inheritance takes two lines of code. Why require a whole new module and call a member function (also 2 lines of code) to do the same thing? Is that REALLY convenient? – flcoder Sep 29 '16 at 20:25
  • `Object.setPrototypeOf(obj, proto)` is a replacement for `obj.__proto__ = proto`. [Here's a discussion on that](http://stackoverflow.com/questions/17759070/difference-between-object-getprototypeof-vs-proto). TLDR; It's a replacement for the hack. – TylerY86 Sep 29 '16 at 20:51
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/124590/discussion-between-flcoder-and-tylery86). – flcoder Sep 29 '16 at 21:30
  • In that "gotcha", it just doesn't make sense to me... I mean... awesome, they thought to inline small functions in order to optimize... cool, but... ok, then, you don't bother to have a way to ignore the comments when considering a function's size? Like... why not just go the extra inch and disregard comments as part of the function size? Surely... there's a way. Also... it makes me wonder... why not inline all functions... why only those under 600. There's plenty I don't know about engines but stuff like that definitely makes me furrow a brow or two. – flcoder Sep 30 '16 at 00:07
  • I think eager inlining was implemented before any IR or AST was generated, not sure if it still works that way for newer V8. I'll investigate it a bit later. – TylerY86 Sep 30 '16 at 00:10
  • Looks like max 600 characters (incl. comment) or 196 AST nodes. Those are defaults that can be overridden by V8 options; `--max_inlined_source_size`, `--max_inlined_nodes` and `--max_inlined_nodes_cumulative`. They must have arrived at these conclusions by just tuning their own code. I think the source-code character cut-off should just be removed though. The AST node count makes more sense, but IR node count would make the most sense. That said, for inlined IR to be generated for other methods... – TylerY86 Sep 30 '16 at 00:39
  • Recompiling to inline common small methods could theoretically be execution cost prohibitive. – TylerY86 Sep 30 '16 at 00:45