Repository: nodejs/node
Pull request: console: optimize single-string logging

After:

```sh
console/log.js
console/log.js variant="plain" n=2000000: 10,760,828.681086333
console/log.js variant="format" n=2000000: 3,104,163.7037100764
console/log.js variant="object" n=2000000: 1,304,646.6897291192
console/log.js variant="group" n=2000000: 5,147,729.648404505
console/log.js variant="info" n=2000000: 10,760,667.090984877
console/log.js variant="warn" n=2000000: 10,730,321.278856428
console/log.js variant="error" n=2000000: 10,739,136.76713795
```


Before:

```sh
console/log.js
console/log.js variant="plain" n=2000000: 6,362,790.401730679
console/log.js variant="format" n=2000000: 3,067,171.4347502897
console/log.js variant="object" n=2000000: 1,260,652.1490095456
console/log.js variant="group" n=2000000: 2,917,442.398797694
console/log.js variant="info" n=2000000: 6,465,884.779549699
console/log.js variant="warn" n=2000000: 6,477,797.477083796
console/log.js variant="error" n=2000000: 6,345,277.494387991
```

This peaked my interest in the flame graph:

* Adds a fast path for when we log one string, which avoids ReflectApply and options validation. inspect has a similar [fast path](https://github.com/gurgunday/node/blob/03fec6a754e4a04a06d90b931d4eb9456727c2a5/lib/internal/util/inspect.js#L2784-L2786), but it's too late for `console.log`.
* Replaces a SafeWeakMap that acts as if it's present in every instance with a symbol. It should be faster since we avoid method calls and `||` on every invocation.

P.S. Couldn't find any benchmarks for `console.log`. And inspect benchmarks won't pick this up...

Changed files:
filename: benchmark/console/log.js
patch: @@ -0,0 +1,78 @@
+'use strict';
+
+// A console throughput benchmark.
+// Uses a custom Console with null Writable streams to avoid I/O latency.
+
+const common = require('../common.js');
+const { Writable } = require('stream');
+const { Console } = require('console');
+
+const bench = common.createBenchmark(main, {
+  n: [2e6],
+  variant: ['plain', 'format', 'object', 'group', 'info', 'warn', 'error'],
+});
+
+class Null extends Writable {
+  _write(chunk, enc, cb) { cb(); }
+}
+
+function makeConsole() {
+  const dn = new Null();
+  return new Console({ stdout: dn, stderr: dn, ignoreErrors: true, colorMode: false });
+}
+
+function main({ n, variant }) {
+  const c = makeConsole();
+
+  switch (variant) {
+    case 'plain': {
+      bench.start();
+      for (let i = 0; i < n; i++) c.log('hello world');
+      bench.end(n);
+      break;
+    }
+    case 'format': {
+      bench.start();
+      for (let i = 0; i < n; i++) c.log('%s %d %j', 'a', 42, { x: 1 });
+      bench.end(n);
+      break;
+    }
+    case 'object': {
+      const obj = { a: 1, b: 2, c: 3 };
+      bench.start();
+      for (let i = 0; i < n; i++) c.log(obj);
+      bench.end(n);
+      break;
+    }
+    case 'group': {
+      bench.start();
+      for (let i = 0; i < n; i++) {
+        c.group('g');
+        c.log('x');
+        c.groupEnd();
+      }
+      bench.end(n);
+      break;
+    }
+    case 'info': {
+      bench.start();
+      for (let i = 0; i < n; i++) c.info('hello world');
+      bench.end(n);
+      break;
+    }
+    case 'warn': {
+      bench.start();
+      for (let i = 0; i < n; i++) c.warn('hello world');
+      bench.end(n);
+      break;
+    }
+    case 'error': {
+      bench.start();
+      for (let i = 0; i < n; i++) c.error('hello world');
+      bench.end(n);
+      break;
+    }
+    default:
+      throw new Error('unknown variant');
+  }
+}

filename: lib/internal/console/constructor.js
patch: @@ -190,7 +190,7 @@ ObjectDefineProperty(Console, SymbolHasInstance, {
 const kColorInspectOptions = { colors: true };
 const kNoColorInspectOptions = {};
 
-const internalIndentationMap = new SafeWeakMap();
+const kGroupIndentationString = Symbol('kGroupIndentationString');
 
 ObjectDefineProperties(Console.prototype, {
   [kBindStreamsEager]: {
@@ -264,6 +264,11 @@ ObjectDefineProperties(Console.prototype, {
           ...consolePropAttributes,
           value: groupIndentation,
         },
+        [kGroupIndentationString]: {
+          __proto__: null,
+          ...consolePropAttributes,
+          value: '',
+        },
         [SymbolToStringTag]: {
           __proto__: null,
           writable: false,
@@ -279,7 +284,7 @@ ObjectDefineProperties(Console.prototype, {
     ...consolePropAttributes,
     value: function(streamSymbol, string) {
       const ignoreErrors = this._ignoreErrors;
-      const groupIndent = internalIndentationMap.get(this) || '';
+      const groupIndent = this[kGroupIndentationString];
 
       const useStdout = streamSymbol === kUseStdout;
       const stream = useStdout ? this._stdout : this._stderr;
@@ -342,6 +347,14 @@ ObjectDefineProperties(Console.prototype, {
     __proto__: null,
     ...consolePropAttributes,
     value: function(args) {
+      if (args.length === 1) {
+        // Fast path: single string, don't call format.
+        // Avoids ReflectApply and validation overhead.
+        const a0 = args[0];
+        if (typeof a0 === 'string') {
+          return a0;
+        }
+      }
       const opts = this[kGetInspectOptions](this._stdout);
       ArrayPrototypeUnshift(args, opts);
       return ReflectApply(formatWithOptions, null, args);
@@ -351,6 +364,14 @@ ObjectDefineProperties(Console.prototype, {
     __proto__: null,
     ...consolePropAttributes,
     value: function(args) {
+      if (args.length === 1) {
+        // Fast path: single string, don't call format.
+        // Avoids ReflectApply and validation overhead.
+        const a0 = args[0];
+        if (typeof a0 === 'string') {
+          return a0;
+        }
+      }
       const opts = this[kGetInspectOptions](this._stderr);
       ArrayPrototypeUnshift(args, opts);
       return ReflectApply(formatWithOptions, null, args);
@@ -513,21 +534,20 @@ const consoleMethods = {
       ReflectApply(this.log, this, data);
     }
 
-    let currentIndentation = internalIndentationMap.get(this) || '';
+    let currentIndentation = this[kGroupIndentationString];
     currentIndentation += StringPrototypeRepeat(' ', this[kGroupIndentationWidth]);
-
-    internalIndentationMap.set(this, currentIndentation);
+    this[kGroupIndentationString] = currentIndentation;
   },
 
   groupEnd() {
-    const currentIndentation = internalIndentationMap.get(this) || '';
+    const currentIndentation = this[kGroupIndentationString];
     const newIndentation = StringPrototypeSlice(
       currentIndentation,
       0,
       currentIndentation.length - this[kGroupIndentationWidth],
     );
 
-    internalIndentationMap.set(this, newIndentation);
+    this[kGroupIndentationString] = newIndentation;
   },
 
   // https://console.spec.whatwg.org/#table