Skip to content

[Bug]: jest runtime drastically slows down calls to native APIs/modules/globals #15502

@carera

Description

@carera

Version

29.7.0

Steps to reproduce

The following snippet includes a set of example calls to native/global APIs (modules? globals objects? not sure what do you call them - Number, Math, Array), and a few simple expressions (equality check, and some multiplication) for comparison:

const ITERATIONS = 100000000

let start = Date.now();
for (let i = 0; i < ITERATIONS; i++) {
  Number.isNaN(i)
}
console.log(Date.now() - start);

start = Date.now();
for (let i = 0; i < ITERATIONS; i++) {
  Array.isArray(i);
}
console.log(Date.now() - start);

start = Date.now();
for (let i = 0; i < ITERATIONS; i++) {
  Math.max(i, i-1);
}
console.log(Date.now() - start);

start = Date.now();
for (let i = 0; i < ITERATIONS; i++) {
  i !== i
}
console.log(Date.now() - start);

start = Date.now();
for (let i = 0; i < ITERATIONS; i++) {
  i * (i-1)
}
console.log(Date.now() - start);

Running with plain NodeJS yields results within a few ms:

> node index.test.js
52
63
48
47
64

Running with jest however yields significant performance drawbacks for the native objects:

> node ./node_modules/.bin/jest index.test.js
  console.log
    11252

      at Object.<anonymous> (index.test.js:7:9)

  console.log
    11080

      at Object.<anonymous> (index.test.js:13:9)

  console.log
    11176

      at Object.<anonymous> (index.test.js:19:9)

  console.log
    48

      at Object.<anonymous> (index.test.js:25:9)

  console.log
    63

      at Object.<anonymous> (index.test.js:31:9)

This happens even when transformation and code coverage is explicitly disable via jest config:

export default {
  collectCoverage: false,
  collectCoverageFrom: [],
  transform: {},
}

Expected behavior

I expect Number.isNaN and other native APIs to run as usual

Actual behavior

Number.isNaN and other native APIs are ~20x slower within jest runtime

Additional context

I tried jest versions all the way down to jest@12, as well as jest@30.0.0-alpha.7, no change.

Profiling hints at _execModule in jest-runtime (that is, if node --prof captures the correct process):

   ticks parent  name
  26056   93.7%  UNKNOWN
  25262   97.0%    JS: *Object.<anonymous> .../index.test.js:1:114
  25262  100.0%      JS: ^_execModule .../node_modules/jest-runtime/build/index.js:1364:14
  25262  100.0%        JS: ^_loadModule .../node_modules/jest-runtime/build/index.js:1000:14
  25262  100.0%          JS: ^requireModule .../node_modules/jest-runtime/build/index.js:812:16
  25262  100.0%            JS: ~jestAdapter .../node_modules/jest-circus/build/legacy-code-todo-rewrite/jestAdapter.js:16:21

Assigning for example Number.isNaN to a variable and calling instead that make the code execute fast again:

const myNaN = Number.isNaN;
myNaN(3);

Environment

System:
    OS: macOS 14.7.2
    CPU: (10) arm64 Apple M1 Pro
  Binaries:
    Node: 20.11.1 - ~/Library/Caches/fnm_multishells/73147_1739329875570/bin/node
    Yarn: 1.22.21 - ~/Library/Caches/fnm_multishells/73147_1739329875570/bin/yarn
    npm: 10.2.4 - ~/Library/Caches/fnm_multishells/73147_1739329875570/bin/npm
  npmPackages:
    jest: ^29.7.0 => 29.7.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions