| name | profiling-optimization |
| description | Profile application performance, identify bottlenecks, and optimize hot paths using CPU profiling, flame graphs, and benchmarking. Use when investigating performance issues or optimizing critical code paths. |
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<string, number>();
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<T>(name: string, fn: () => Promise<T>): Promise<T> {
const start = performance.now();
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);
});
2. 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<void> {
if (!this.session) return;
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');
3. 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')
4. Benchmarking
class Benchmark {
async run(
name: string,
fn: () => Promise<any>,
iterations: number = 1000
): Promise<void> {
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<any> }>,
iterations: number = 1000
): Promise<void> {
for (const impl of implementations) {
await this.run(impl.name, impl.fn, iterations);
}
}
}
// 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;
}
}
]);
5. 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));
6. 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<K, V> {
private cache = new Map<K, V>();
private maxSize: number;
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);
}
}
}
2. Lazy Loading
class LazyValue<T> {
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