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