Profiling & Performance
The profiling mindset
Section titled “The profiling mindset”Before optimising anything, you need to measure. The same rule applies in both TypeScript and Python: do not guess where the bottleneck is — profile first, optimise second, measure again.
| Node.js tool | Python equivalent |
|---|---|
console.time / console.timeEnd | timeit.timeit() |
performance.now() | time.perf_counter() |
node --prof + node --prof-process | cProfile + pstats |
| Chrome DevTools flame graph | snakeviz (pip install snakeviz) |
clinic.js / 0x | py-spy (external, sampling profiler) |
timeit: precise microbenchmarks
Section titled “timeit: precise microbenchmarks”timeit runs a snippet thousands of times and reports the total time. It is the Python equivalent of console.time but much more rigorous — it disables garbage collection during the run to reduce noise.
// TypeScript — manual timingconsole.time("list comp");for (let i = 0; i < 10_000; i++) { Array.from({ length: 100 }, (_, i) => i ** 2);}console.timeEnd("list comp");import timeit
# timeit runs the snippet 10,000 times; returns total secondst = timeit.timeit( '[x**2 for x in range(100)]', number=10_000)print(f"List comprehension: {t:.4f}s")
# Compare two approachest2 = timeit.timeit( 'list(map(lambda x: x**2, range(100)))', number=10_000)print(f"map(): {t2:.4f}s")Try it — timeit in the browser
Section titled “Try it — timeit in the browser”import timeit
# Compare list comprehension vs map vs generator-to-listapproaches = { "list comp": '[x**2 for x in range(100)]', "map+list ": 'list(map(lambda x: x**2, range(100)))', "for loop ": 'r=[]; [r.append(x**2) for x in range(100)]',}
results = {}for name, stmt in approaches.items(): t = timeit.timeit(stmt, number=5000) results[name] = t print(f" {name}: {t:.4f}s")
fastest = min(results, key=results.get)print(f"\nFastest: {fastest}")Loading Python runtime (first run only)…
cProfile: function-level profiling
Section titled “cProfile: function-level profiling”cProfile is Python’s built-in deterministic profiler. It records every function call, how many times each was called, and how long each took. It is the equivalent of node --prof but ships with Python and needs no extra tools to read.
// Node.js — file-based profiling// node --prof server.js// node --prof-process isolate-*.log > profile.txtimport cProfileimport pstatsimport io
def slow_function(): total = 0 for i in range(1000): total += sum(range(i)) return total
# Profile a specific functionpr = cProfile.Profile()pr.enable()slow_function()pr.disable()
# Print top 5 by cumulative times = io.StringIO()ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')ps.print_stats(5)print(s.getvalue())cProfile in the browser
Section titled “cProfile in the browser”import cProfileimport pstatsimport io
def inner(n: int) -> int: return sum(range(n))
def middle(n: int) -> int: return sum(inner(i) for i in range(n))
def outer(n: int) -> int: return middle(n) + inner(n)
pr = cProfile.Profile()pr.enable()result = outer(200)pr.disable()
s = io.StringIO()ps = pstats.Stats(pr, stream=s).sort_stats("cumulative")ps.print_stats(8)print(f"Result: {result}")print()print(s.getvalue())Loading Python runtime (first run only)…
Reading pstats output
Section titled “Reading pstats output”ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.004 0.004 script.py:4(slow_function) 1000 0.004 0.000 0.004 0.000 {built-in method builtins.sum}| Column | Meaning |
|---|---|
ncalls | Number of times the function was called |
tottime | Time spent in this function (excluding callees) |
cumtime | Cumulative time (including all sub-calls) |
percall | Average time per call |
Sort strategies:
sort_stats('cumulative')— find the slowest end-to-end call chains (start here)sort_stats('tottime')— find functions doing the most work themselvessort_stats('ncalls')— find functions called unexpectedly often
Workflow: profile → identify → fix → measure
Section titled “Workflow: profile → identify → fix → measure”# 1. Profileimport cProfilecProfile.run('my_function()', 'profile.stats')
# 2. Inspect interactivelyimport pstatsp = pstats.Stats('profile.stats')p.sort_stats('cumulative').print_stats(10)
# 3. Visualise (install once)# pip install snakeviz# snakeviz profile.stats # opens browser flame graph