Skip to content

ESM import of http is slower compared to CommonJS #59686

@bpasero

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');
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    docIssues and PRs related to the documentations.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