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

lib: improve lazy loading #14167

Closed
wants to merge 4 commits into from
Closed

Conversation

BridgeAR
Copy link
Member

@BridgeAR BridgeAR commented Jul 11, 2017

The function call plus the existence check is more expensive than inlining the lazy require.

A few lazyAssert calls were obsolete as the assert would have to be required before.

I also moved a few lazy requires in the specific code path as it would not be required at all in some cases.

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)

lib

The lazy assert should already be in place when calling any
of the E functions.
@nodejs-github-bot nodejs-github-bot added the lib / src Issues and PRs related to general changes in the lib or src directory. label Jul 11, 2017
@refack
Copy link
Contributor

refack commented Jul 11, 2017

I don't like it as is. It spreads the require / process.binding, making it hard to audit dependencies.
What you could do is, close to the top:

Object.defineProperty(this, 'lasyErrors', {
  get() {
    const value = require('errors');
    Object.defineProperty(this, 'lasyErrors', {value});
    return value;
  }
);

@Fishrock123
Copy link
Contributor

Does this expense actually add up to anything significant in a realistic scenario?

@BridgeAR
Copy link
Member Author

@refack I guess you mean something like the following?

const lazy = {};

Object.defineProperty(lazy, 'util', {
  configurable: true,
  get() {
    const value = require('util');
    Object.defineProperty(lazy, 'util', { value, enumerable: true });
    return value;
  }
});

const util = lazy.util;

I know that spreading is not nice but if it's a function call or not is not much of a difference in that case.

@Fishrock123 I'll run some benchmarks later on. I just checked the different lazy loading times them self and those are way faster.

@refack
Copy link
Contributor

refack commented Jul 11, 2017

I know that spreading is not nice but if it's a function call or not is not much of a difference in that case.

I agree that dynamically it's the same, the difference is in static analysis, if it's a function (or property) at the top it's easier to spot, visually or with tools.

As for a property you can set it on the module or on an explicit lazy holder. Then use modules.util or lazy.util all over

@refack
Copy link
Contributor

refack commented Jul 11, 2017

Actually lazy is a better choice (I keep discovering that and forgetting it):

const lazy = {
  get util() {
    const value = require('util');
    Object.defineProperty(this, 'util', { value, enumerable: true });
    return value;
  },
  ...
}

@jasnell
Copy link
Member

jasnell commented Jul 12, 2017

Overall I'm not a fan. I prefer to keep lazy loading only when absolutely necessary, mostly to avoid circular references caused by early loading.

}
return _lazyConstants;
}
var lazyConstants;
Copy link
Contributor

Choose a reason for hiding this comment

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

Re #14167 (comment) comment this could be inlined.

errors = require('internal/errors');
return errors;
}
var lazyError;
Copy link
Contributor

Choose a reason for hiding this comment

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

Inline

@@ -5,24 +5,12 @@ const prefix = `(${process.release.name}:${process.pid}) `;

exports.setup = setupProcessWarnings;

var errors;
var fs;
var lazyErrors;
Copy link
Contributor

Choose a reason for hiding this comment

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

errors can be inlined

@refack
Copy link
Contributor

refack commented Jul 12, 2017

Simpler which is definitely better 👍
What about the lazy property holder? Should be performant, and readable...

Do not lazy load if not necessary and prevent function calls.
@BridgeAR
Copy link
Member Author

@refack there's still the property lookup and it is still faster to just check if the variable is defined or not. This was the way how lazy loading was used in other spots as well and with the simple if it should hopefully be readable again.

@refack
Copy link
Contributor

refack commented Jul 12, 2017

@refack there's still the property lookup and it is still faster to just check if the variable is defined or not. This was the way how lazy loading was used in other spots as well and with the simple if it should hopefully be readable again.

Ack
(but only because we took a %200 startup slowdown hit and have been chasing timing out tests all day 😉)

lib/net.js Outdated
var cluster;
var dns;
// Lazily loaded
var cluster = null;
Copy link
Member Author

Choose a reason for hiding this comment

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

This could probably be required up front as well but cluster is likely not required in many places that use net and therefore I kept it as is.

Copy link
Contributor

Choose a reason for hiding this comment

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

So could you document that:

// `cluster` is only used by `listenInCluster` so for startup performance
// reasons it's lazy loaded.

@TimothyGu
Copy link
Member

LGTM.

Aside from this PR, I'm not too big of a fan of using assert in the code base anywhere other than in tests. Unlike assert(3), the module is unnecessarily big for something that can usually be done in two lines.

@BridgeAR
Copy link
Member Author

@refack I added the comment.

I also moved a statement to prevent lazy loading under some conditions.

@refack
Copy link
Contributor

refack commented Jul 19, 2017

The link to the CI: https://ci.nodejs.org/job/node-test-commit/11256/ ✔️

refack pushed a commit to refack/node that referenced this pull request Jul 19, 2017
* internal/errors - assert should already be in place when calling any
  of the message generating functions.
* No lazy load if not necessary.
* Replace function calls with `if`s.

PR-URL: nodejs#14167
Reviewed-By: Refael Ackermann <[email protected]>
Reviewed-By: Timothy Gu <[email protected]>
@refack
Copy link
Contributor

refack commented Jul 19, 2017

Landed in b55ab01

@refack refack closed this Jul 19, 2017
@addaleax
Copy link
Member

This doesn’t land cleanly on 8.x; if you can, please follow the guide and raise a backport PR, if you don’t think it make sense let me know and we’ll add the dont-land-on label.

@jasnell
Copy link
Member

jasnell commented Sep 20, 2017

Ping @BridgeAR .. re: backport.

BridgeAR added a commit to BridgeAR/node that referenced this pull request Sep 21, 2017
* internal/errors - assert should already be in place when calling any
  of the message generating functions.
* No lazy load if not necessary.
* Replace function calls with `if`s.

PR-URL: nodejs#14167
Reviewed-By: Refael Ackermann <[email protected]>
Reviewed-By: Timothy Gu <[email protected]>
jasnell pushed a commit that referenced this pull request Sep 21, 2017
* internal/errors - assert should already be in place when calling any
  of the message generating functions.
* No lazy load if not necessary.
* Replace function calls with `if`s.

PR-URL: #14167
Reviewed-By: Refael Ackermann <[email protected]>
Reviewed-By: Timothy Gu <[email protected]>
@MylesBorins
Copy link
Contributor

Should this be backported to v6.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

@BridgeAR
Copy link
Member Author

I do not see the necessity to backport this. So I changed the labels accordingly.

@BridgeAR BridgeAR deleted the improve-lazy-loading branch April 1, 2019 23:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
lib / src Issues and PRs related to general changes in the lib or src directory.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants