Skip to content

Instantly share code, notes, and snippets.

@nazarhussain
Created June 16, 2025 11:50
Show Gist options
  • Save nazarhussain/38c7c0c782cab065c95c96fe19887387 to your computer and use it in GitHub Desktop.
Save nazarhussain/38c7c0c782cab065c95c96fe19887387 to your computer and use it in GitHub Desktop.
Perfomance comparison for `Bun.spawn` vs `node:child_process` compatibility layer
#!/usr/bin/env bun
import { spawn, exec } from 'node:child_process';
import { promisify } from 'node:util';
const execAsync = promisify(exec);
// Utility functions
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const formatBytes = (bytes) => {
const sizes = ['B', 'KB', 'MB', 'GB'];
if (bytes === 0) return '0 B';
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
};
const getMemoryUsage = () => {
const usage = process.memoryUsage();
return {
rss: usage.rss,
heapUsed: usage.heapUsed,
heapTotal: usage.heapTotal,
external: usage.external
};
};
const calculateStats = (times) => {
const sorted = times.sort((a, b) => a - b);
const len = sorted.length;
const sum = sorted.reduce((a, b) => a + b, 0);
return {
min: sorted[0],
max: sorted[len - 1],
mean: sum / len,
median: len % 2 === 0 ? (sorted[len/2-1] + sorted[len/2]) / 2 : sorted[Math.floor(len/2)],
p95: sorted[Math.floor(len * 0.95)],
p99: sorted[Math.floor(len * 0.99)]
};
};
// Benchmark functions using Bun.spawn
const bunSpawnBenchmarks = {
async simpleCommand() {
const start = performance.now();
const proc = Bun.spawn(['echo', 'hello']);
await proc.exited;
return performance.now() - start;
},
async longRunningProcess() {
const start = performance.now();
const proc = Bun.spawn(['sleep', '0.5']);
await proc.exited;
return performance.now() - start;
},
async ioHeavyOperation() {
const start = performance.now();
const proc = Bun.spawn(['find', '/tmp', '-name', '*.txt'], {
stdout: 'pipe',
stderr: 'pipe'
});
await proc.exited;
return performance.now() - start;
},
async nodeSubprocess() {
const start = performance.now();
const proc = Bun.spawn(['node', '-e', 'console.log("test")'], {
stdout: 'pipe'
});
await proc.exited;
return performance.now() - start;
},
async withStdio() {
const start = performance.now();
const proc = Bun.spawn(['cat'], {
stdin: 'pipe',
stdout: 'pipe'
});
proc.stdin.write('hello world\n');
proc.stdin.end();
await proc.exited;
return performance.now() - start;
}
};
// Benchmark functions using node:child_process
const nodeSpawnBenchmarks = {
async simpleCommand() {
const start = performance.now();
return new Promise((resolve, reject) => {
const proc = spawn('echo', ['hello']);
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
},
async longRunningProcess() {
const start = performance.now();
return new Promise((resolve, reject) => {
const proc = spawn('sleep', ['0.5']);
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
},
async ioHeavyOperation() {
const start = performance.now();
return new Promise((resolve, reject) => {
const proc = spawn('find', ['/tmp', '-name', '*.txt']);
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
},
async nodeSubprocess() {
const start = performance.now();
return new Promise((resolve, reject) => {
const proc = spawn('node', ['-e', 'console.log("test")']);
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
},
async withStdio() {
const start = performance.now();
return new Promise((resolve, reject) => {
const proc = spawn('cat', [], { stdio: 'pipe' });
proc.stdin.write('hello world\n');
proc.stdin.end();
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
}
};
// Concurrent benchmarks
const concurrentBenchmarks = {
async bunConcurrent(count = 10) {
const start = performance.now();
const promises = Array.from({ length: count }, () => {
const proc = Bun.spawn(['echo', 'concurrent']);
return proc.exited;
});
await Promise.all(promises);
return performance.now() - start;
},
async nodeConcurrent(count = 10) {
const start = performance.now();
const promises = Array.from({ length: count }, () => {
return new Promise((resolve, reject) => {
const proc = spawn('echo', ['concurrent']);
proc.on('close', resolve);
proc.on('error', reject);
});
});
await Promise.all(promises);
return performance.now() - start;
}
};
// Sequential rapid spawning benchmarks
const rapidSequentialBenchmarks = {
async bunRapidSequential(count = 50) {
const times = [];
for (let i = 0; i < count; i++) {
const start = performance.now();
const proc = Bun.spawn(['echo', `test-${i}`]);
await proc.exited;
times.push(performance.now() - start);
}
return times;
},
async nodeRapidSequential(count = 50) {
const times = [];
for (let i = 0; i < count; i++) {
const start = performance.now();
const time = await new Promise((resolve, reject) => {
const proc = spawn('echo', [`test-${i}`]);
proc.on('close', () => {
resolve(performance.now() - start);
});
proc.on('error', reject);
});
times.push(time);
}
return times;
}
};
// Main benchmark runner
async function runBenchmark(name, bunFn, nodeFn, iterations = 100) {
console.log(`\nπŸ”₯ Running ${name} benchmark (${iterations} iterations)`);
console.log('=' .repeat(60));
// Warmup
console.log('Warming up...');
for (let i = 0; i < 5; i++) {
try { await bunFn(); } catch {}
try { await nodeFn(); } catch {}
}
// Force garbage collection if available
if (global.gc) {
global.gc();
}
const bunTimes = [];
const nodeTimes = [];
const bunMemory = [];
const nodeMemory = [];
// Bun.spawn benchmark
console.log('Testing Bun.spawn...');
for (let i = 0; i < iterations; i++) {
const memBefore = getMemoryUsage();
try {
const time = await bunFn();
bunTimes.push(time);
const memAfter = getMemoryUsage();
bunMemory.push(memAfter.rss - memBefore.rss);
} catch (error) {
console.warn(`Bun iteration ${i} failed:`, error.message);
}
}
// node:child_process benchmark
console.log('Testing node:child_process...');
for (let i = 0; i < iterations; i++) {
const memBefore = getMemoryUsage();
try {
const time = await nodeFn();
nodeTimes.push(time);
const memAfter = getMemoryUsage();
nodeMemory.push(memAfter.rss - memBefore.rss);
} catch (error) {
console.warn(`Node iteration ${i} failed:`, error.message);
}
}
// Calculate and display results
const bunStats = calculateStats(bunTimes);
const nodeStats = calculateStats(nodeTimes);
const bunMemStats = calculateStats(bunMemory);
const nodeMemStats = calculateStats(nodeMemory);
console.log('\nπŸ“Š Results:');
console.log('\nTime (ms):');
console.log(`Bun.spawn - Mean: ${bunStats.mean.toFixed(2)}, Median: ${bunStats.median.toFixed(2)}, P95: ${bunStats.p95.toFixed(2)}, P99: ${bunStats.p99.toFixed(2)}`);
console.log(`node:child_process - Mean: ${nodeStats.mean.toFixed(2)}, Median: ${nodeStats.median.toFixed(2)}, P95: ${nodeStats.p95.toFixed(2)}, P99: ${nodeStats.p99.toFixed(2)}`);
const speedup = nodeStats.mean / bunStats.mean;
console.log(`\nπŸš€ Bun.spawn is ${speedup.toFixed(2)}x ${speedup > 1 ? 'faster' : 'slower'} than node:child_process`);
console.log('\nMemory Delta:');
console.log(`Bun.spawn - Mean: ${formatBytes(bunMemStats.mean)}, P95: ${formatBytes(bunMemStats.p95)}`);
console.log(`node:child_process - Mean: ${formatBytes(nodeMemStats.mean)}, P95: ${formatBytes(nodeMemStats.p95)}`);
}
async function runConcurrentBenchmark(name, bunFn, nodeFn, counts = [10, 50, 100]) {
console.log(`\nπŸ”₯ Running ${name} concurrent benchmark`);
console.log('=' .repeat(60));
for (const count of counts) {
console.log(`\nTesting with ${count} concurrent processes:`);
// Bun test
const bunTimes = [];
for (let i = 0; i < 5; i++) {
const time = await bunFn(count);
bunTimes.push(time);
}
const bunStats = calculateStats(bunTimes);
// Node test
const nodeTimes = [];
for (let i = 0; i < 5; i++) {
const time = await nodeFn(count);
nodeTimes.push(time);
}
const nodeStats = calculateStats(nodeTimes);
console.log(`Bun.spawn: ${bunStats.mean.toFixed(2)}ms (${(count / (bunStats.mean / 1000)).toFixed(0)} processes/sec)`);
console.log(`node:child_process: ${nodeStats.mean.toFixed(2)}ms (${(count / (nodeStats.mean / 1000)).toFixed(0)} processes/sec)`);
const speedup = nodeStats.mean / bunStats.mean;
console.log(`Bun is ${speedup.toFixed(2)}x ${speedup > 1 ? 'faster' : 'slower'}`);
}
}
async function runRapidSequentialBenchmark() {
console.log(`\nπŸ”₯ Running rapid sequential benchmark`);
console.log('=' .repeat(60));
const count = 50;
console.log('Testing Bun.spawn rapid sequential...');
const bunTimes = await rapidSequentialBenchmarks.bunRapidSequential(count);
console.log('Testing node:child_process rapid sequential...');
const nodeTimes = await rapidSequentialBenchmarks.nodeRapidSequential(count);
const bunStats = calculateStats(bunTimes);
const nodeStats = calculateStats(nodeTimes);
console.log('\nπŸ“Š Rapid Sequential Results:');
console.log(`Bun.spawn - Mean: ${bunStats.mean.toFixed(2)}ms, P95: ${bunStats.p95.toFixed(2)}ms`);
console.log(`node:child_process - Mean: ${nodeStats.mean.toFixed(2)}ms, P95: ${nodeStats.p95.toFixed(2)}ms`);
const speedup = nodeStats.mean / bunStats.mean;
console.log(`\nπŸš€ Bun.spawn is ${speedup.toFixed(2)}x ${speedup > 1 ? 'faster' : 'slower'} than node:child_process`);
}
// Main execution
async function main() {
console.log('πŸ§ͺ Bun.spawn vs node:child_process Performance Benchmark');
console.log(`Running on Bun ${Bun.version}`);
console.log(`Node.js compatibility layer enabled`);
try {
// Individual benchmarks
await runBenchmark('Simple Command', bunSpawnBenchmarks.simpleCommand, nodeSpawnBenchmarks.simpleCommand);
await runBenchmark('Long Running Process', bunSpawnBenchmarks.longRunningProcess, nodeSpawnBenchmarks.longRunningProcess);
await runBenchmark('I/O Heavy Operation', bunSpawnBenchmarks.ioHeavyOperation, nodeSpawnBenchmarks.ioHeavyOperation);
await runBenchmark('Node.js Subprocess', bunSpawnBenchmarks.nodeSubprocess, nodeSpawnBenchmarks.nodeSubprocess);
await runBenchmark('With stdio', bunSpawnBenchmarks.withStdio, nodeSpawnBenchmarks.withStdio);
// Concurrent benchmarks
await runConcurrentBenchmark('Concurrent', concurrentBenchmarks.bunConcurrent, concurrentBenchmarks.nodeConcurrent);
// Rapid sequential benchmark
await runRapidSequentialBenchmark();
console.log('\nβœ… Benchmark completed!');
console.log('\nπŸ’‘ Tips:');
console.log('- Run with --expose-gc for more accurate memory measurements');
console.log('- Run multiple times and average results for production decisions');
console.log('- Consider your specific use case when interpreting results');
} catch (error) {
console.error('❌ Benchmark failed:', error);
process.exit(1);
}
}
// Run the benchmark
if (import.meta.main) {
main();
}
@nazarhussain
Copy link
Author

Results on

Darwin Nazars-MacBook-Pro-2.fritz.box 24.5.0 Darwin Kernel Version 24.5.0: Tue Apr 22 19:54:29 PDT 2025; root:xnu-11417.121.6~2/RELEASE_ARM64_T6030 arm64

❯ bun run --bun --expose-gc bun-vs-node.ts
πŸ§ͺ Bun.spawn vs node:child_process Performance Benchmark
Running on Bun 1.2.13
Node.js compatibility layer enabled

πŸ”₯ Running Simple Command benchmark (100 iterations)
============================================================
Warming up...
Testing Bun.spawn...
Testing node:child_process...

πŸ“Š Results:

Time (ms):
Bun.spawn       - Mean: 1.23, Median: 1.17, P95: 1.71, P99: 1.99
node:child_process - Mean: 1.30, Median: 1.27, P95: 1.63, P99: 2.45

πŸš€ Bun.spawn is 1.06x faster than node:child_process

Memory Delta:
Bun.spawn       - Mean: 20.64 KB, P95: 144 KB
node:child_process - Mean: 92.48 KB, P95: 464 KB

πŸ”₯ Running Long Running Process benchmark (100 iterations)
============================================================
Warming up...
Testing Bun.spawn...
Testing node:child_process...

πŸ“Š Results:

Time (ms):
Bun.spawn       - Mean: 506.98, Median: 506.99, P95: 507.98, P99: 508.70
node:child_process - Mean: 507.24, Median: 507.33, P95: 508.44, P99: 510.14

πŸš€ Bun.spawn is 1.00x faster than node:child_process

Memory Delta:
Bun.spawn       - Mean: 13.12 KB, P95: 64 KB
node:child_process - Mean: NaN undefined, P95: 128 KB

πŸ”₯ Running I/O Heavy Operation benchmark (100 iterations)
============================================================
Warming up...
Testing Bun.spawn...
Testing node:child_process...

πŸ“Š Results:

Time (ms):
Bun.spawn       - Mean: 1.31, Median: 1.26, P95: 1.76, P99: 2.91
node:child_process - Mean: 1.31, Median: 1.27, P95: 1.49, P99: 3.86

πŸš€ Bun.spawn is 1.00x slower than node:child_process

Memory Delta:
Bun.spawn       - Mean: 35.36 KB, P95: 80 KB
node:child_process - Mean: 13.6 KB, P95: 32 KB

πŸ”₯ Running Node.js Subprocess benchmark (100 iterations)
============================================================
Warming up...
Testing Bun.spawn...
Testing node:child_process...

πŸ“Š Results:

Time (ms):
Bun.spawn       - Mean: 7.09, Median: 6.99, P95: 7.84, P99: 10.22
node:child_process - Mean: 7.02, Median: 7.02, P95: 7.44, P99: 7.82

πŸš€ Bun.spawn is 0.99x slower than node:child_process

Memory Delta:
Bun.spawn       - Mean: 11.04 KB, P95: 64 KB
node:child_process - Mean: 31.36 KB, P95: 192 KB

πŸ”₯ Running With stdio benchmark (100 iterations)
============================================================
Warming up...
Testing Bun.spawn...
Testing node:child_process...

πŸ“Š Results:

Time (ms):
Bun.spawn       - Mean: 1.11, Median: 1.10, P95: 1.26, P99: 1.37
node:child_process - Mean: 1.17, Median: 1.16, P95: 1.30, P99: 1.36

πŸš€ Bun.spawn is 1.05x faster than node:child_process

Memory Delta:
Bun.spawn       - Mean: 163.84 B, P95: 0 B
node:child_process - Mean: 22.88 KB, P95: 192 KB

πŸ”₯ Running Concurrent concurrent benchmark
============================================================

Testing with 10 concurrent processes:
Bun.spawn: 2.94ms (3399 processes/sec)
node:child_process: 3.22ms (3104 processes/sec)
Bun is 1.09x faster

Testing with 50 concurrent processes:
Bun.spawn: 10.53ms (4747 processes/sec)
node:child_process: 11.99ms (4169 processes/sec)
Bun is 1.14x faster

Testing with 100 concurrent processes:
Bun.spawn: 19.08ms (5240 processes/sec)
node:child_process: 21.32ms (4691 processes/sec)
Bun is 1.12x faster

πŸ”₯ Running rapid sequential benchmark
============================================================
Testing Bun.spawn rapid sequential...
Testing node:child_process rapid sequential...

πŸ“Š Rapid Sequential Results:
Bun.spawn       - Mean: 1.10ms, P95: 1.26ms
node:child_process - Mean: 1.13ms, P95: 1.29ms

πŸš€ Bun.spawn is 1.03x faster than node:child_process

βœ… Benchmark completed!

πŸ’‘ Tips:
- Run with --expose-gc for more accurate memory measurements
- Run multiple times and average results for production decisions
- Consider your specific use case when interpreting results

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