| name | performance-profiling |
| description | Profile and optimize Python/Node.js — cProfile, py-spy, clinic.js, memory profiling, async bottlenecks, DB query analysis |
| triggers | ["performance profiling","profile python","profile nodejs","cprofile","py-spy","memory leak python","memory profiling","slow query analysis","performance bottleneck","async performance","optimize python"] |
| do_not_use_for | ["frontend performance — use web-performance skill","load testing — use k6/locust","error debugging — use error-handling-patterns"] |
| see_also | ["ai-code-maintainability","logging-observability","web-performance"] |
Performance Profiling
Python: cProfile (CPU)
python -m cProfile -s cumulative -o profile.prof my_script.py
pip install snakeviz
snakeviz profile.prof
import cProfile
import pstats
from pstats import SortKey
profiler = cProfile.Profile()
profiler.enable()
result = slow_function(data)
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats(SortKey.CUMULATIVE)
stats.print_stats(20)
Python: py-spy (Production-safe sampling)
pip install py-spy
py-spy top --pid 12345
py-spy record -o profile.svg --pid 12345
py-spy dump --pid 12345
py-spy record -o profile.svg -- python my_script.py
Python: Memory Profiling
import tracemalloc
tracemalloc.start()
snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics("lineno")
for stat in top_stats[:10]:
print(stat)
pip install memory-profiler
from memory_profiler import profile
@profile
def process_large_file(path: str) -> None:
with open(path) as f:
data = f.read()
process(data)
Python: Async Bottlenecks
import asyncio
import time
async def timed(coro, label: str):
start = time.perf_counter()
result = await coro
elapsed = time.perf_counter() - start
if elapsed > 0.1:
print(f"SLOW [{label}]: {elapsed:.3f}s")
return result
async def main():
tasks = [
timed(fetch_user(uid), f"fetch_user({uid})")
for uid in user_ids
]
results = await asyncio.gather(*tasks)
import asyncio
async def detect_blocking(threshold: float = 0.05):
"""Alert if anything blocks the event loop > 50ms."""
while True:
t0 = time.perf_counter()
await asyncio.sleep(0)
elapsed = time.perf_counter() - t0
if elapsed > threshold:
print(f"Event loop blocked for {elapsed:.3f}s!")
DB Query Analysis
import logging
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
from sqlalchemy_utils import QueryChain
with QueryChain() as qc:
users = session.execute(select(User)).scalars().all()
print(f"Queries: {qc.count}, Total: {qc.total_time:.3f}s")
for q in qc.queries:
print(f" {q.duration:.3f}s — {q.statement[:80]}")
from sqlalchemy import text
result = session.execute(text(
"EXPLAIN ANALYZE SELECT * FROM users WHERE email = :email"
), {"email": "test@example.com"})
for row in result:
print(row[0])
Node.js: clinic.js
npm install -g clinic
clinic doctor -- node server.js
clinic flame -- node server.js
clinic bubbleprof -- node server.js
Quick Win Checklist
for item in items:
result = self.config.timeout * self.config.multiplier
timeout = self.config.timeout
multiplier = self.config.multiplier
for item in items:
result = timeout * multiplier
data = [transform(x) for x in large_file]
data = (transform(x) for x in large_file)
result = ""
for chunk in chunks:
result += chunk
result = "".join(chunks)
allowed = ["admin", "editor", "viewer"]
if role in allowed:
ALLOWED_ROLES = frozenset({"admin", "editor", "viewer"})
if role in ALLOWED_ROLES:
Anti-Fake-Pass Checks
- cProfile adds overhead — don't profile micro-benchmarks with it, use
timeit instead
- py-spy requires
--sudo for other user processes on Linux
- Memory profiler
@profile decorator slows code significantly — remove in prod
asyncio.gather() parallelizes I/O, not CPU — use ProcessPoolExecutor for CPU-bound
- "EXPLAIN ANALYZE" actually runs the query — don't use on destructive queries
- Flame graphs show wall clock time — CPU flame graphs only show CPU time, not I/O waits