From f13cc3237ee7e8ecd9675b29b8a48d21bb081561 Mon Sep 17 00:00:00 2001
From: Brian White <mscdex@mscdex.net>
Date: Fri, 16 Mar 2018 20:35:32 -0400
Subject: [PATCH] stream: improve stream creation performance

PR-URL: https://github.com/nodejs/node/pull/19401
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
---
 benchmark/streams/creation.js           | 55 +++++++++++++++++++++++++
 benchmark/streams/transform-creation.js | 21 ----------
 lib/_stream_readable.js                 | 11 +++--
 lib/_stream_writable.js                 | 16 ++++---
 4 files changed, 73 insertions(+), 30 deletions(-)
 create mode 100644 benchmark/streams/creation.js
 delete mode 100644 benchmark/streams/transform-creation.js

diff --git a/benchmark/streams/creation.js b/benchmark/streams/creation.js
new file mode 100644
index 00000000000000..67187f91bd9cb1
--- /dev/null
+++ b/benchmark/streams/creation.js
@@ -0,0 +1,55 @@
+'use strict';
+const common = require('../common.js');
+const Duplex = require('stream').Duplex;
+const Readable = require('stream').Readable;
+const Transform = require('stream').Transform;
+const Writable = require('stream').Writable;
+
+const bench = common.createBenchmark(main, {
+  n: [50e6],
+  kind: ['duplex', 'readable', 'transform', 'writable']
+});
+
+function main({ n, kind }) {
+  var i = 0;
+  switch (kind) {
+    case 'duplex':
+      new Duplex({});
+      new Duplex();
+
+      bench.start();
+      for (; i < n; ++i)
+        new Duplex();
+      bench.end(n);
+      break;
+    case 'readable':
+      new Readable({});
+      new Readable();
+
+      bench.start();
+      for (; i < n; ++i)
+        new Readable();
+      bench.end(n);
+      break;
+    case 'writable':
+      new Writable({});
+      new Writable();
+
+      bench.start();
+      for (; i < n; ++i)
+        new Writable();
+      bench.end(n);
+      break;
+    case 'transform':
+      new Transform({});
+      new Transform();
+
+      bench.start();
+      for (; i < n; ++i)
+        new Transform();
+      bench.end(n);
+      break;
+    default:
+      throw new Error('Invalid kind');
+  }
+}
diff --git a/benchmark/streams/transform-creation.js b/benchmark/streams/transform-creation.js
deleted file mode 100644
index abfab0c8e25321..00000000000000
--- a/benchmark/streams/transform-creation.js
+++ /dev/null
@@ -1,21 +0,0 @@
-'use strict';
-const common = require('../common.js');
-const Transform = require('stream').Transform;
-const inherits = require('util').inherits;
-
-const bench = common.createBenchmark(main, {
-  n: [1e6]
-});
-
-function MyTransform() {
-  Transform.call(this);
-}
-inherits(MyTransform, Transform);
-MyTransform.prototype._transform = function() {};
-
-function main({ n }) {
-  bench.start();
-  for (var i = 0; i < n; ++i)
-    new MyTransform();
-  bench.end(n);
-}
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index c8116811ba5dbd..725cc2651873db 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -56,7 +56,7 @@ function prependListener(emitter, event, fn) {
     emitter._events[event] = [fn, emitter._events[event]];
 }
 
-function ReadableState(options, stream) {
+function ReadableState(options, stream, isDuplex) {
   options = options || {};
 
   // Duplex streams are both readable and writable, but share
@@ -64,7 +64,8 @@ function ReadableState(options, stream) {
   // However, some cases require setting options to different
   // values for the readable and the writable sides of the duplex stream.
   // These options can be provided separately as readableXXX and writableXXX.
-  var isDuplex = stream instanceof Stream.Duplex;
+  if (typeof isDuplex !== 'boolean')
+    isDuplex = stream instanceof Stream.Duplex;
 
   // object stream flag. Used to make read(n) ignore n and to
   // make all the buffer merging and length checks go away
@@ -142,7 +143,11 @@ function Readable(options) {
   if (!(this instanceof Readable))
     return new Readable(options);
 
-  this._readableState = new ReadableState(options, this);
+  // Checking for a Stream.Duplex instance is faster here instead of inside
+  // the ReadableState constructor, at least with V8 6.5
+  const isDuplex = (this instanceof Stream.Duplex);
+
+  this._readableState = new ReadableState(options, this, isDuplex);
 
   // legacy
   this.readable = true;
diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js
index f927d59491fb8c..f51950c6b11f84 100644
--- a/lib/_stream_writable.js
+++ b/lib/_stream_writable.js
@@ -39,7 +39,7 @@ util.inherits(Writable, Stream);
 
 function nop() {}
 
-function WritableState(options, stream) {
+function WritableState(options, stream, isDuplex) {
   options = options || {};
 
   // Duplex streams are both readable and writable, but share
@@ -47,7 +47,8 @@ function WritableState(options, stream) {
   // However, some cases require setting options to different
   // values for the readable and the writable sides of the duplex stream.
   // These options can be provided separately as readableXXX and writableXXX.
-  var isDuplex = stream instanceof Stream.Duplex;
+  if (typeof isDuplex !== 'boolean')
+    isDuplex = stream instanceof Stream.Duplex;
 
   // object stream flag to indicate whether or not this stream
   // contains buffers or objects.
@@ -200,12 +201,15 @@ function Writable(options) {
   // Trying to use the custom `instanceof` for Writable here will also break the
   // Node.js LazyTransform implementation, which has a non-trivial getter for
   // `_writableState` that would lead to infinite recursion.
-  if (!(realHasInstance.call(Writable, this)) &&
-      !(this instanceof Stream.Duplex)) {
+
+  // Checking for a Stream.Duplex instance is faster here instead of inside
+  // the WritableState constructor, at least with V8 6.5
+  const isDuplex = (this instanceof Stream.Duplex);
+
+  if (!isDuplex && !realHasInstance.call(Writable, this))
     return new Writable(options);
-  }
 
-  this._writableState = new WritableState(options, this);
+  this._writableState = new WritableState(options, this, isDuplex);
 
   // legacy.
   this.writable = true;