Profiling & Optimization Overview
Profile code execution to identify performance bottlenecks and optimize critical paths using data-driven approaches.
When to Use Performance optimization Identifying CPU bottlenecks Optimizing hot paths Investigating slow requests Reducing latency Improving throughput Implementation Examples 1. Node.js Profiling import { performance, PerformanceObserver } from 'perf_hooks';
class Profiler {
private marks = new Map
mark(name: string): void { this.marks.set(name, performance.now()); }
measure(name: string, startMark: string): number {
const start = this.marks.get(startMark);
if (!start) throw new Error(Mark ${startMark} not found);
const duration = performance.now() - start;
console.log(`${name}: ${duration.toFixed(2)}ms`);
return duration;
}
async profile
try {
return await fn();
} finally {
const duration = performance.now() - start;
console.log(`${name}: ${duration.toFixed(2)}ms`);
}
} }
// Usage const profiler = new Profiler();
app.get('/api/users', async (req, res) => { profiler.mark('request-start');
const users = await profiler.profile('fetch-users', async () => { return await db.query('SELECT * FROM users'); });
profiler.measure('total-request-time', 'request-start');
res.json(users); });
- Chrome DevTools CPU Profile import inspector from 'inspector'; import fs from 'fs';
class CPUProfiler { private session: inspector.Session | null = null;
start(): void { this.session = new inspector.Session(); this.session.connect();
this.session.post('Profiler.enable');
this.session.post('Profiler.start');
console.log('CPU profiling started');
}
async stop(outputFile: string): Promise
this.session.post('Profiler.stop', (err, { profile }) => {
if (err) {
console.error('Profiling error:', err);
return;
}
fs.writeFileSync(outputFile, JSON.stringify(profile));
console.log(`Profile saved to ${outputFile}`);
this.session!.disconnect();
this.session = null;
});
} }
// Usage const cpuProfiler = new CPUProfiler();
// Start profiling cpuProfiler.start();
// Run code to profile await runExpensiveOperation();
// Stop and save await cpuProfiler.stop('./profile.cpuprofile');
- Python cProfile import cProfile import pstats from pstats import SortKey import io
class Profiler: def init(self): self.profiler = cProfile.Profile()
def __enter__(self):
self.profiler.enable()
return self
def __exit__(self, *args):
self.profiler.disable()
def print_stats(self, sort_by: str = 'cumulative'):
"""Print profiling statistics."""
s = io.StringIO()
ps = pstats.Stats(self.profiler, stream=s)
if sort_by == 'time':
ps.sort_stats(SortKey.TIME)
elif sort_by == 'cumulative':
ps.sort_stats(SortKey.CUMULATIVE)
elif sort_by == 'calls':
ps.sort_stats(SortKey.CALLS)
ps.print_stats(20) # Top 20
print(s.getvalue())
def save_stats(self, filename: str):
"""Save profiling data."""
self.profiler.dump_stats(filename)
Usage
with Profiler() as prof: # Code to profile result = expensive_function()
prof.print_stats('cumulative') prof.save_stats('profile.prof')
-
Benchmarking class Benchmark { async run( name: string, fn: () => Promise
, iterations: number = 1000 ): Promise { console.log( \nBenchmarking: ${name});const times: number[] = [];
// Warmup for (let i = 0; i < 10; i++) { await fn(); }
// Actual benchmark for (let i = 0; i < iterations; i++) { const start = performance.now(); await fn(); times.push(performance.now() - start); }
// Statistics const sorted = times.sort((a, b) => a - b); const min = sorted[0]; const max = sorted[sorted.length - 1]; const avg = times.reduce((a, b) => a + b, 0) / times.length; const p50 = sorted[Math.floor(sorted.length * 0.5)]; const p95 = sorted[Math.floor(sorted.length * 0.95)]; const p99 = sorted[Math.floor(sorted.length * 0.99)];
console.log(
Iterations: ${iterations}); console.log(Min: ${min.toFixed(2)}ms); console.log(Max: ${max.toFixed(2)}ms); console.log(Avg: ${avg.toFixed(2)}ms); console.log(P50: ${p50.toFixed(2)}ms); console.log(P95: ${p95.toFixed(2)}ms); console.log(P99: ${p99.toFixed(2)}ms); }
async compare(
implementations: Array<{ name: string; fn: () => Promise
// Usage const bench = new Benchmark();
await bench.compare([ { name: 'Array.filter + map', fn: async () => { const arr = Array.from({ length: 1000 }, (, i) => i); return arr.filter(x => x % 2 === 0).map(x => x * 2); } }, { name: 'Single loop', fn: async () => { const arr = Array.from({ length: 1000 }, (, i) => i); const result = []; for (const x of arr) { if (x % 2 === 0) { result.push(x * 2); } } return result; } } ]);
- Database Query Profiling import { Pool } from 'pg';
class QueryProfiler { constructor(private pool: Pool) {}
async profileQuery(query: string, params: any[] = []): Promise<{ result: any; planningTime: number; executionTime: number; plan: any; }> { // Enable timing await this.pool.query('SET track_io_timing = ON');
// Get query plan
const explainResult = await this.pool.query(
`EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ${query}`,
params
);
const plan = explainResult.rows[0]['QUERY PLAN'][0];
// Execute actual query
const start = performance.now();
const result = await this.pool.query(query, params);
const duration = performance.now() - start;
return {
result: result.rows,
planningTime: plan['Planning Time'],
executionTime: plan['Execution Time'],
plan
};
}
formatPlan(plan: any): string {
let output = 'Query Plan:\n';
output += Planning Time: ${plan['Planning Time']}ms\n;
output += Execution Time: ${plan['Execution Time']}ms\n\n;
const formatNode = (node: any, indent: number = 0) => {
const prefix = ' '.repeat(indent);
output += `${prefix}${node['Node Type']}\n`;
output += `${prefix} Cost: ${node['Total Cost']}\n`;
output += `${prefix} Rows: ${node['Actual Rows']}\n`;
output += `${prefix} Time: ${node['Actual Total Time']}ms\n`;
if (node.Plans) {
node.Plans.forEach((child: any) => formatNode(child, indent + 1));
}
};
formatNode(plan.Plan);
return output;
} }
// Usage const profiler = new QueryProfiler(pool);
const { result, planningTime, executionTime, plan } = await profiler.profileQuery( 'SELECT * FROM users WHERE age > $1', [25] );
console.log(profiler.formatPlan(plan));
- Flame Graph Generation
Generate flame graph using 0x
npx 0x -o flamegraph.html node server.js
Or using clinic.js
npx clinic doctor --on-port 'autocannon localhost:3000' -- node server.js npx clinic flame --on-port 'autocannon localhost:3000' -- node server.js
Optimization Techniques
1. Caching
class LRUCache
constructor(maxSize: number = 100) { this.maxSize = maxSize; }
get(key: K): V | undefined { if (!this.cache.has(key)) return undefined;
// Move to end (most recently used)
const value = this.cache.get(key)!;
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key: K, value: V): void { // Remove if exists if (this.cache.has(key)) { this.cache.delete(key); }
// Add to end
this.cache.set(key, value);
// Evict oldest if over capacity
if (this.cache.size > this.maxSize) {
const oldest = this.cache.keys().next().value;
this.cache.delete(oldest);
}
} }
- Lazy Loading
class LazyValue
{ private value?: T; private loaded = false;
constructor(private loader: () => T) {}
get(): T { if (!this.loaded) { this.value = this.loader(); this.loaded = true; } return this.value!; } }
// Usage const expensive = new LazyValue(() => { console.log('Computing expensive value...'); return computeExpensiveValue(); });
// Only computed when first accessed const value = expensive.get();
Best Practices ✅ DO Profile before optimizing Focus on hot paths Measure impact of changes Use production-like data Consider memory vs speed tradeoffs Document optimization rationale ❌ DON'T Optimize without profiling Ignore readability for minor gains Skip benchmarking Optimize cold paths Make changes without measurement Tools Node.js: 0x, clinic.js, node --prof Python: cProfile, py-spy, memory_profiler Visualization: Flame graphs, Chrome DevTools Database: EXPLAIN ANALYZE, pg_stat_statements Resources 0x Flame Graph Profiler Chrome DevTools Profiling Python cProfile