正在加载,请稍候…

Node.js Performance Profiling: Finding and Fixing Bottlenecks

Profile and optimize Node.js applications. Learn CPU profiling with clinic.js, memory leak detection, event loop monitoring, and fixing common performance issues.

Node.js Performance Profiling

CPU Profiling with clinic.js

# Install clinic.js
npm install -g clinic

# CPU profiler (flame graphs)
clinic flame -- node server.js

# Doctor (event loop, memory, CPU overview)
clinic doctor -- node server.js

# Bubbleprof (async bottlenecks)
clinic bubbleprof -- node server.js

Built-in Node.js Profiler

# Start with profiling enabled
node --prof server.js

# Run load test
npx autocannon http://localhost:3000/api/heavy -d 10

# Process profiling data
node --prof-process isolate-0x*.log > profile.txt

# View flame chart
node --prof-process --preprocess isolate-0x*.log | flamebearer

Memory Leak Detection

// Detect memory growth
const startMemory = process.memoryUsage();
setInterval(() => {
  const mem = process.memoryUsage();
  const heapGrowth = mem.heapUsed - startMemory.heapUsed;

  if (heapGrowth > 100 * 1024 * 1024) { // 100MB growth
    console.warn('Potential memory leak detected', {
      heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + 'MB',
      heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + 'MB',
      rss: Math.round(mem.rss / 1024 / 1024) + 'MB',
    });
  }
}, 30000);
# Heap snapshot with node-inspector
node --inspect server.js
# Open Chrome DevTools -> Memory -> Take Heap Snapshot
# Compare snapshots before and after suspected leak

# heapdump programmatically
npm install heapdump
import heapdump from 'heapdump';

process.on('SIGUSR2', () => {
  const filename = `/tmp/heapdump-${Date.now()}.heapsnapshot`;
  heapdump.writeSnapshot(filename, (err, filename) => {
    if (!err) console.log('Heap snapshot saved:', filename);
  });
});
// Trigger: kill -SIGUSR2 <pid>

Event Loop Monitoring

import { monitorEventLoopDelay } from 'perf_hooks';

const h = monitorEventLoopDelay({ resolution: 20 });
h.enable();

setInterval(() => {
  const mean = h.mean / 1e6; // Convert to ms
  const p99 = h.percentile(99) / 1e6;

  if (p99 > 100) {
    console.warn(`High event loop delay: mean=${mean.toFixed(1)}ms, p99=${p99.toFixed(1)}ms`);
  }

  h.reset();
}, 5000);

Common Performance Issues

// Problem: Synchronous operations blocking event loop
import fs from 'fs';

// Bad: Blocks event loop
const data = fs.readFileSync('/large-file.json');

// Good: Non-blocking
const data = await fs.promises.readFile('/large-file.json');

// Problem: Parsing large JSON synchronously
// Bad: Blocks for large objects
const parsed = JSON.parse(largeJsonString);

// Good: Use streams for large data
import { createReadStream } from 'fs';
import { createInterface } from 'readline';

const rl = createInterface({ input: createReadStream('data.ndjson') });
for await (const line of rl) {
  const item = JSON.parse(line);
  await processItem(item);
}

// Problem: CPU-intensive tasks on main thread
// Use worker_threads for CPU work
import { Worker, isMainThread, parentPort, workerData } from 'worker_threads';

if (!isMainThread) {
  const result = heavyComputation(workerData.input);
  parentPort!.postMessage(result);
}

async function runInWorker(input: unknown): Promise<unknown> {
  return new Promise((resolve, reject) => {
    const worker = new Worker(__filename, { workerData: { input } });
    worker.on('message', resolve);
    worker.on('error', reject);
  });
}

Benchmarking with autocannon

# Basic benchmark
npx autocannon http://localhost:3000/api/users -d 10 -c 100

# Options:
# -d 10      duration 10 seconds
# -c 100     100 connections
# -p 10      10 pipelining requests

# Compare before/after optimization
npx autocannon http://localhost:3000/api/users -d 30 -c 50 --json > before.json
# Make optimization
npx autocannon http://localhost:3000/api/users -d 30 -c 50 --json > after.json

Profile before optimizing - most performance issues are in 10% of the code.