Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

util: improve util.inspect performance #14492

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions benchmark/util/inspect-array.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';

const common = require('../common');
const util = require('util');

const bench = common.createBenchmark(main, {
n: [1e2],
len: [1e5],
type: [
'denseArray',
'sparseArray',
'mixedArray'
]
});

function main(conf) {
const { n, len, type } = conf;
var arr = Array(len);
var i;

switch (type) {
case 'denseArray':
arr = arr.fill(0);
break;
case 'sparseArray':
break;
case 'mixedArray':
for (i = 0; i < n; i += 2)
arr[i] = i;
break;
default:
throw new Error(`Unsupported type ${type}`);
}
bench.start();
for (i = 0; i < n; i++) {
util.inspect(arr);
}
bench.end(n);
}
44 changes: 26 additions & 18 deletions lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ const inspectDefaultOptions = Object.seal({

const numbersOnlyRE = /^\d+$/;

const objectHasOwnProperty = Object.prototype.hasOwnProperty;
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
const regExpToString = RegExp.prototype.toString;
const dateToISOString = Date.prototype.toISOString;
Expand Down Expand Up @@ -672,22 +671,36 @@ function formatArray(ctx, value, recurseTimes, visibleKeys, keys) {
var output = [];
let visibleLength = 0;
let index = 0;
while (index < value.length && visibleLength < ctx.maxArrayLength) {
let emptyItems = 0;
while (index < value.length && !hasOwnProperty(value, String(index))) {
emptyItems++;
index++;
}
if (emptyItems > 0) {
for (const elem of keys) {
if (visibleLength === ctx.maxArrayLength)
break;
// Symbols might have been added to the keys
if (typeof elem !== 'string')
continue;
const i = +elem;
if (index !== i) {
// Skip zero and negative numbers as well as non numbers
if (i > 0 === false)
continue;
const emptyItems = i - index;
const ending = emptyItems > 1 ? 's' : '';
const message = `<${emptyItems} empty item${ending}>`;
output.push(ctx.stylize(message, 'undefined'));
} else {
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
String(index), true));
index++;
index = i;
if (++visibleLength === ctx.maxArrayLength)
break;
}
output.push(formatProperty(ctx, value, recurseTimes, visibleKeys,
elem, true));
visibleLength++;
index++;
}
if (index < value.length && visibleLength !== ctx.maxArrayLength) {
const len = value.length - index;
const ending = len > 1 ? 's' : '';
const message = `<${len} empty item${ending}>`;
output.push(ctx.stylize(message, 'undefined'));
index = value.length;
}
var remaining = value.length - index;
if (remaining > 0) {
Expand Down Expand Up @@ -803,7 +816,7 @@ function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) {
str = ctx.stylize('[Setter]', 'special');
}
}
if (!hasOwnProperty(visibleKeys, key)) {
if (visibleKeys[key] === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be worth to eventually make visibleKeys an ES2015 Set instead of emulating it via an object, but I don't know how it will affect performance with the current V8 (if it will at all).

Copy link
Member Author

@BridgeAR BridgeAR Jul 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I looked at the code there were a couple more optimizations possible but I tried to be more surgical here. E.g. for arrays there is no need for Object.keys when using for in and ctx.seen should be a Set instead of an Array.

if (typeof key === 'symbol') {
name = `[${ctx.stylize(key.toString(), 'symbol')}]`;
} else {
Expand Down Expand Up @@ -982,11 +995,6 @@ function _extend(target, source) {
return target;
}

function hasOwnProperty(obj, prop) {
return objectHasOwnProperty.call(obj, prop);
}


// Deprecated old stuff.

function print(...args) {
Expand Down
18 changes: 17 additions & 1 deletion test/parallel/test-util-inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,24 @@ assert.strictEqual(
get: function() { this.push(true); return this.length; }
}
);
Object.defineProperty(
value,
'-1',
{
enumerable: true,
value: -1
}
);
assert.strictEqual(util.inspect(value),
'[ 1, 2, 3, growingLength: [Getter] ]');
'[ 1, 2, 3, growingLength: [Getter], \'-1\': -1 ]');
}

// Array with inherited number properties
{
class CustomArray extends Array {};
CustomArray.prototype[5] = 'foo';
const arr = new CustomArray(50);
assert.strictEqual(util.inspect(arr), 'CustomArray [ <50 empty items> ]');
}

// Function with properties
Expand Down