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

Improve performance #16

Merged
merged 4 commits into from
Jun 15, 2022
Merged

Improve performance #16

merged 4 commits into from
Jun 15, 2022

Conversation

ehmicky
Copy link
Contributor

@ehmicky ehmicky commented Jun 13, 2022

Fixes #12

This improves the performance.

Benchmarks on Node 18.3.0 on my machine, before the change:

undefined                                         : 491.366ms
0                                                 : 545.934ms
0n                                                : 8.169s
''                                                : 730.961ms
true                                              : 689.757ms
Symbol()                                          : 8.272s
[Function (anonymous)]                            : 687.628ms
[Function: namedFunc]                             : 679.116ms
null                                              : 508.197ms
{}                                                : 1.493s
Object [Math] {}                                  : 3.589s
Set(0) {}                                         : 3.841s
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 2.408s
Promise { undefined }                             : 2.397s
[Object: null prototype] {}                       : 2.138s
Locale [Intl.Locale] {}                           : 3.197s
{ prop: true }                                    : 1.480s
Class {}                                          : 3.277s
[]                                                : 726.733ms
/regexp/                                          : 758.549ms
function Error() { [native code] }                : 721.147ms
2022-06-13T21:32:59.396Z                          : 692.619ms
[Arguments] {}                                    : 652.13ms
{}                                                : 13.990s

After the change:

undefined                                         : 161.508ms
0                                                 : 122.998ms
0n                                                : 121.546ms
''                                                : 121.174ms
true                                              : 121.634ms
Symbol()                                          : 121.894ms
[Function (anonymous)]                            : 121.306ms
[Function: namedFunc]                             : 121.261ms
null                                              : 143.305ms
{}                                                : 1.081s
Object [Math] {}                                  : 1.141s
Set(0) {}                                         : 980.147ms
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 969.084ms
Promise { undefined }                             : 968.839ms
[Object: null prototype] {}                       : 2.520s
Locale [Intl.Locale] {}                           : 985.919ms
{ prop: true }                                    : 2.397s
Class {}                                          : 979.786ms
[]                                                : 966.707ms
/regexp/                                          : 973.039ms
function Error() { [native code] }                : 966.822ms
2022-06-13T21:31:59.071Z                          : 972.304ms
[Arguments] {}                                    : 3.602s
{}                                                : 10.165s

In a nutshell:

  • Some less-used native types (symbol, bigint) are 60 times faster
  • Most native types (undefined, null, number, string, boolean) and functions are 5 times faster
  • Plain objects are 50% times faster. This is important since they are the most likely argument.
  • Class instances and many core object types (Set, Promise, ArrayBuffer, Math, Intl.*, arguments) are 2-4 times faster
  • Proxy is 30% faster
  • Object.create(null) is 10% slower
  • Array, RegExp, Error, Date are 30% slower. This is the main drawback. However, this seems worthwhile based on the other improvements.
  • new Object() is twice slower. It is rarely used though.

@ehmicky ehmicky mentioned this pull request Jun 13, 2022
index.js Outdated
return false;
}

const prototype = Object.getPrototypeOf(value);
return prototype === null || prototype === Object.prototype;
return (prototype === null || prototype === Object.prototype) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

benchmark.js Outdated
new Proxy({}, {})
];

const runLoop = function (value) {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
const runLoop = function (value) {
const runLoop = value => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed 👍

benchmark.js Outdated
import {inspect} from 'node:util';
import isPlainObject from 'is-plain-obj';

const runBenchmarks = function () {
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
const runBenchmarks = function () {
const runBenchmarks = () => {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed 👍

@ehmicky ehmicky requested a review from sindresorhus June 14, 2022 13:29
@sindresorhus
Copy link
Owner

Can you fix the conflict?

@ehmicky
Copy link
Contributor Author

ehmicky commented Jun 14, 2022

Fixed.

I have also added a small additional performance improvement at 58de3a2. Object.getPrototypeOf() comes at a performance cost. The new change optimizes the most common case: the argument is a plain object, in which case only 1 (instead of 2) Object.getPrototypeOf() needs to be done. This does not impact the performance much for non-plain-objects, but this improves the performance for plain objects greatly.

Before:

undefined                                         : 120.848ms
0                                                 : 123.221ms
0n                                                : 121.69ms
''                                                : 122.007ms
true                                              : 123.68ms
Symbol()                                          : 123.947ms
[Function (anonymous)]                            : 122.496ms
[Function: namedFunc]                             : 121.66ms
null                                              : 141.828ms
{}                                                : 1.739s
Object [Math] {}                                  : 1.837s
Set(0) {}                                         : 1.757s
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 1.765s
Promise { undefined }                             : 1.747s
[Object: null prototype] {}                       : 2.602s
Locale [Intl.Locale] {}                           : 1.742s
{ prop: true }                                    : 3.184s
Class {}                                          : 1.743s
[]                                                : 1.747s
/regexp/                                          : 1.805s
function Error() { [native code] }                : 1.763s
2022-06-14T16:40:07.517Z                          : 1.746s
[Arguments] {}                                    : 4.325s
{}                                                : 10.887s

After:

undefined                                         : 162.474ms
0                                                 : 122.287ms
0n                                                : 121.877ms
''                                                : 121.334ms
true                                              : 121.582ms
Symbol()                                          : 120.901ms
[Function (anonymous)]                            : 120.956ms
[Function: namedFunc]                             : 121.22ms
null                                              : 141.049ms
{}                                                : 1.136s
Object [Math] {}                                  : 1.126s
Set(0) {}                                         : 1.713s
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 1.697s
Promise { undefined }                             : 1.703s
[Object: null prototype] {}                       : 2.527s
Locale [Intl.Locale] {}                           : 1.800s
{ prop: true }                                    : 3.647s
Class {}                                          : 1.827s
[]                                                : 1.827s
/regexp/                                          : 1.819s
function Error() { [native code] }                : 1.818s
2022-06-14T16:42:26.235Z                          : 1.835s
[Arguments] {}                                    : 3.984s
{}                                                : 10.167s

Note: you can see the time for objects that are not plain objects is now roughly twice slower compared to the first benchmarks published above. This is unrelated to this PR: this is due to the cross-realm PR (#14) which added an additional Object.getPrototypeOf() to the logic (#14). Unfortunately, that additional feature comes at a performance cost for non-plain objects.

@ehmicky
Copy link
Contributor Author

ehmicky commented Jun 14, 2022

For completeness, those are benchmarks before/after both this PR and the cross-realm PR (#14). Before:

undefined                                         : 545.663ms
0                                                 : 591.312ms
0n                                                : 9.436s
''                                                : 838.412ms
true                                              : 786.609ms
Symbol()                                          : 9.429s
[Function (anonymous)]                            : 752.251ms
[Function: namedFunc]                             : 773.338ms
null                                              : 564.996ms
{}                                                : 1.661s
Object [Math] {}                                  : 3.891s
Set(0) {}                                         : 4.195s
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 2.678s
Promise { undefined }                             : 3.159s
[Object: null prototype] {}                       : 2.586s
Locale [Intl.Locale] {}                           : 3.797s
{ prop: true }                                    : 1.725s
Class {}                                          : 3.597s
[]                                                : 846.749ms
/regexp/                                          : 848.499ms
function Error() { [native code] }                : 844.467ms
2022-06-14T16:56:35.326Z                          : 797.05ms
[Arguments] {}                                    : 748.679ms
{}                                                : 16.165s

After:

undefined                                         : 165.378ms
0                                                 : 123.947ms
0n                                                : 121.877ms
''                                                : 122.252ms
true                                              : 123.71ms
Symbol()                                          : 123.806ms
[Function (anonymous)]                            : 122.966ms
[Function: namedFunc]                             : 122.122ms
null                                              : 142.183ms
{}                                                : 1.125s
Object [Math] {}                                  : 1.143s
Set(0) {}                                         : 1.718s
ArrayBuffer { [Uint8Contents]: <>, byteLength: 0 }: 1.705s
Promise { undefined }                             : 1.703s
[Object: null prototype] {}                       : 2.567s
Locale [Intl.Locale] {}                           : 1.792s
{ prop: true }                                    : 3.600s
Class {}                                          : 1.800s
[]                                                : 1.801s
/regexp/                                          : 1.798s
function Error() { [native code] }                : 1.790s
2022-06-14T16:55:42.385Z                          : 1.786s
[Arguments] {}                                    : 3.901s
{}                                                : 10.792s

@sindresorhus sindresorhus merged commit e334e11 into sindresorhus:main Jun 15, 2022
@sindresorhus
Copy link
Owner

Nice work :)

@sindresorhus
Copy link
Owner

@ehmicky ehmicky deleted the perf branch June 15, 2022 15:39
@ehmicky
Copy link
Contributor Author

ehmicky commented Jun 15, 2022

Sure! Done at sindresorhus/is#169

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Performance improvement
2 participants