Skip to content

ESM import of http is slower compared to CommonJS #59686

Description

@bpasero

Version

v22.18.0

Platform

Darwin MBP-14-Work.local 24.6.0 Darwin Kernel Version 24.6.0: Mon Jul 14 11:30:40 PDT 2025; root:xnu-11417.140.69~1/RELEASE_ARM64_T6041 arm64

Subsystem

No response

What steps will reproduce the bug?

Run the following performance test from the command line:

#!/usr/bin/env node

const { spawn } = require('child_process');
const fs = require('fs');
const path = require('path');

// Create test files
const withHttpCode = `
import { STATUS_CODES } from 'http';
console.log(Object.keys(STATUS_CODES).length);
const startTime = process.hrtime.bigint();
const endTime = process.hrtime.bigint();
console.log(Number(endTime - startTime) / 1000000); // Convert to milliseconds
`;

const withoutHttpCode = `
const { STATUS_CODES } = require('http');
console.log(Object.keys(STATUS_CODES).length);
const startTime = process.hrtime.bigint();
const endTime = process.hrtime.bigint();
console.log(Number(endTime - startTime) / 1000000); // Convert to milliseconds
`;

// Write test files
fs.writeFileSync('with-http.js', withHttpCode);
fs.writeFileSync('without-http.js', withoutHttpCode);

async function runBenchmark() {
  const iterations = 100;
  const withHttpTimes = [];
  const withoutHttpTimes = [];

  console.log(`Running benchmark with ${iterations} iterations...\n`);

  // Benchmark with http import
  console.log('Testing with http import...');
  for (let i = 0; i < iterations; i++) {
    const startTime = process.hrtime.bigint();

    const child = spawn('node', ['with-http.js'], { stdio: 'pipe' });

    await new Promise((resolve) => {
      let output = '';
      child.stdout.on('data', (data) => {
        output += data.toString();
      });

      child.on('close', () => {
        const endTime = process.hrtime.bigint();
        const totalTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds
        withHttpTimes.push(totalTime);
        resolve();
      });
    });

    if (i % 10 === 0) process.stdout.write('.');
  }
  console.log(' Done!');

  // Benchmark without http import
  console.log('Testing without http import...');
  for (let i = 0; i < iterations; i++) {
    const startTime = process.hrtime.bigint();

    const child = spawn('node', ['without-http.js'], { stdio: 'pipe' });

    await new Promise((resolve) => {
      let output = '';
      child.stdout.on('data', (data) => {
        output += data.toString();
      });

      child.on('close', () => {
        const endTime = process.hrtime.bigint();
        const totalTime = Number(endTime - startTime) / 1000000; // Convert to milliseconds
        withoutHttpTimes.push(totalTime);
        resolve();
      });
    });

    if (i % 10 === 0) process.stdout.write('.');
  }
  console.log(' Done!\n');

  // Calculate statistics
  function calculateStats(times) {
    const sorted = times.sort((a, b) => a - b);
    const sum = times.reduce((a, b) => a + b, 0);
    const avg = sum / times.length;
    const median = sorted[Math.floor(sorted.length / 2)];
    const min = Math.min(...times);
    const max = Math.max(...times);

    return { avg, median, min, max };
  }

  const withHttpStats = calculateStats(withHttpTimes);
  const withoutHttpStats = calculateStats(withoutHttpTimes);

  // Display results
  console.log('='.repeat(60));
  console.log('BENCHMARK RESULTS');
  console.log('='.repeat(60));

  console.log('\nWith HTTP import:');
  console.log(`  Average:  ${withHttpStats.avg.toFixed(2)} ms`);
  console.log(`  Median:   ${withHttpStats.median.toFixed(2)} ms`);
  console.log(`  Min:      ${withHttpStats.min.toFixed(2)} ms`);
  console.log(`  Max:      ${withHttpStats.max.toFixed(2)} ms`);

  console.log('\nWithout HTTP import:');
  console.log(`  Average:  ${withoutHttpStats.avg.toFixed(2)} ms`);
  console.log(`  Median:   ${withoutHttpStats.median.toFixed(2)} ms`);
  console.log(`  Min:      ${withoutHttpStats.min.toFixed(2)} ms`);
  console.log(`  Max:      ${withoutHttpStats.max.toFixed(2)} ms`);

  const avgDiff = withHttpStats.avg - withoutHttpStats.avg;
  const medianDiff = withHttpStats.median - withoutHttpStats.median;

  console.log('\nDifference (with HTTP - without HTTP):');
  console.log(`  Average:  ${avgDiff.toFixed(2)} ms (${((avgDiff / withoutHttpStats.avg) * 100).toFixed(1)}%)`);
  console.log(`  Median:   ${medianDiff.toFixed(2)} ms (${((medianDiff / withoutHttpStats.median) * 100).toFixed(1)}%)`);

  console.log('\n' + '='.repeat(60));

  // Cleanup
  fs.unlinkSync('with-http.js');
  fs.unlinkSync('without-http.js');
}

// Run the benchmark
runBenchmark().catch(console.error);

How often does it reproduce? Is there a required condition?

Always when I run the performance test.

What is the expected behavior? Why is that the expected behavior?

Importing a constant from http should not be significantly slower when using import vs require.

What do you see instead?

Using import is a lot slower:

Running benchmark with 100 iterations...

Testing with http import...
.......... Done!
Testing without http import...
.......... Done!

============================================================
BENCHMARK RESULTS
============================================================

With HTTP import:
  Average:  26.88 ms
  Median:   26.08 ms
  Min:      25.19 ms
  Max:      67.84 ms

Without HTTP import:
  Average:  17.54 ms
  Median:   17.44 ms
  Min:      16.66 ms
  Max:      21.56 ms

Difference (with HTTP - without HTTP):
  Average:  9.33 ms (53.2%)
  Median:   8.64 ms (49.5%)

============================================================

Additional information

Is it possible that the lazy undici method gets run when using import?

node/lib/http.js

Lines 118 to 124 in 5af0355

/**
* Lazy loads WebSocket, CloseEvent and MessageEvent classes from undici
* @returns {object} An object containing WebSocket, CloseEvent, and MessageEvent classes.
*/
function lazyUndici() {
return undici ??= require('internal/deps/undici/undici');
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to Node.js documentation.good first issueIssues that are suitable for first-time contributors.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions