Skip to content

Improve performance of accessing globals in scripts running in vm modules #31658

Open
@SimenB

Description

@SimenB

Is your feature request related to a problem? Please describe.

Accessing globals from within a vm.Script is guarded by interceptors which makes accessing them slow, (from my understanding, @fhinkel has an excellent comment explaining some of this here: jestjs/jest#5163 (comment)). The suggested workaround is to evaluate and cache the global you want access to (Math in my example case below) and inject that into the vm.Script. This is an acceptable workaround for vm.Script and vm.compileFunction but it cannot be done when using ESM vm.SourceTextModule as there is no wrapping function call.

I've put together this script you can run to see the numbers:

// test.js
const {
  compileFunction,
  createContext,
  runInContext,
  SourceTextModule
} = require("vm");
const context = createContext({ console });

const sourceCode = `
  const runTimes = 10000000;
  let sum = 0;
  console.time();
  for (let i = 0; i < runTimes; i++) {
    sum += Math.abs(0);
  }
  console.timeEnd();
`;

(async () => {
  const compiledFunction = compileFunction(sourceCode, [], {
    parsingContext: context
  });

  const vmModule = new SourceTextModule(sourceCode, { context });

  await vmModule.link(() => {
    throw new Error("should never be called");
  });

  // These are both super slow (1.6 seconds on my machine)
  compiledFunction();
  await vmModule.evaluate();

  // however, this is quick (less than 10 milliseconds on my machine)
  const compiledWithMathFunction = compileFunction(sourceCode, ["Math"], {
    parsingContext: context
  });
  compiledWithMathFunction(runInContext("Math", context));
})().catch(error => {
  console.error(error);
  process.exitCode = 1;
});

Running this gives the following results on my machine:

$ node --experimental-vm-modules test.js
(node:19958) ExperimentalWarning: VM Modules is an experimental feature. This feature could change at any time
default: 1.698s
default: 1.620s
default: 7.306ms

So ~1600ms if not using the workaround, which reduces it to ~7ms.

Describe the solution you'd like

I'd like for accessing globals to be as fast, or as close to as possible, as fast as accessing globals outside of vm. Would it be possible for Node's vm implementation to cache the global property lookups, so that the price is only paid once instead of on every single access?

Describe alternatives you've considered

As mentioned, caching and injecting the global manually works when there is a function wrapper, but this doesn't work with ESM. I've tried assigning the Math global to the context, and while that halves the time spent (~800ms), it's still 2 orders of magnitude slower than injecting a reference.

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceIssues and PRs related to the performance of Node.js.v8 engineIssues and PRs related to the V8 dependency.vmIssues and PRs related to the vm subsystem.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions