Skip to content

Measure before optimising

Speed up where the time is: a part's share of the time caps what any fix to it can give, so measure before and after.

Predict first

Before you read: predict

A payments ledger's monthly statement page takes 1,200 ms. A trace of one statement puts 480 ms in Postgres queries, 600 ms in rendering the PDF and 120 ms in the rest. The team wants the page three times as fast and plans a Redis cache for those queries. Options are rounded to one decimal place.

However fast the cache makes the queries, what is the most it can speed up the page?

Pick one

The 30-second version

Set a target for a named input, then measure where that input's time goes before changing anything. A fix shrinks only its own part: a part holding share p of the time, made k times faster, leaves the whole 1 ÷ ((1 − p) + p ÷ k) times as fast, never beyond 1 ÷ (1 − p). Fix the biggest share, then measure again.

Speed up where the time is: a part's share of the time caps what any fix to it can give, so measure before and after.

How it works

Step 1 of 11

A target. Write the goal as a number for a named input, such as "under 800 ms for the March statement", before any change.

Analogy: A removal firm's foreman times a house move (household removals)

The firm's owner wants a faster lorry, because "moves take all day".

On the house move In the request
"the lorry is slow" a guess that names a suspect (step 3)
the foreman's timesheet from last week's three-bedroom move: packing 4 h, loading 1 h, the drive 1 h, unloading 2 h the measured split of one named input (step 2)
a lorry twice as fast saves half an hour of the eight only the part shrinks (step 4)
a lorry that arrived the moment it left would save one hour, so the day is at best 8 ÷ 7 as quick the cap (step 6)
a second packing crew halves packing and saves two hours the biggest share first (step 7)
the foreman times the next three-bedroom move, not a studio flat, and unloading now takes as long as packing measure again, same input (step 11)

Breaks down when: a move's stages follow one another, while a request's parts can overlap; a request that queries and renders at once has shares that no longer add up to its total, and only a trace shows which part waits.

Contrasting case: a firm that mostly moves people across the country. There the drive is the biggest share, and the same timesheet points at the faster lorry: the measurement decides, not the habit.

In real terms: time the parts of one named job, speed up the biggest, and time the same job again.

Examples, run for real

4.1 ExamHall EXH-2460: the results page and the Redis proposal

GET /results/:examId took a second for exam 7's results, and the team proposed Redis in front of the SQL. A trace of that request split the time first:

4.1 GET /results/:examId, exam 7: 1,000 ms = SQL 200 + PDF and JSON rendering 600 + other 200
4.1 Redis in front of the SQL, 10 times faster: SQL 200 -> 20 ms · page 820 ms · 1.22 times as fast
4.1 the SQL infinitely fast (the cap): SQL 200 -> 0 ms · page 800 ms · 1.25 times as fast
4.1 MR !2760, rendering halved: rendering 600 -> 300 ms · page 700 ms · 1.43 times as fast

A cache ten times faster saves 180 ms, and even a free query saves only 200 ms: no cache can make this page more than 1.25 times as fast (steps 4–6). MR !2760 halves the PDF and JSON rendering instead, the biggest share, and saves 300 ms (step 7).

4.2 The same input, profiled before and after

4.2 node --cpu-prof --cpu-prof-dir=<dir> results-fixture.mjs exam-7.json (before MR !2760): exam-7: 120 rows rendered, 120 lines · 1 .cpuprofile written, a JSON profile with nodes, samples and timeDeltas
4.2 node --cpu-prof --cpu-prof-dir=<dir> results-fixture.mjs exam-7.json --halved (after MR !2760, same input): exam-7: 120 rows rendered, 120 lines (rendering halved) · 1 .cpuprofile written, a JSON profile with nodes, samples and timeDeltas
4.2 graded: nothing (the profiles are a precondition; their samples and times vary from run to run)

Node writes one .cpuprofile per run when the process exits, into --cpu-prof-dir; open each in DevTools' Performance panel and compare like with like (steps 2 and 11). A profile samples the stack about once a millisecond, so its counts differ from run to run: read shares from the same input, never one run's single numbers.

Try it

Answer first, then check. Nothing is saved.

Question 1 of 5

A merchant's March statement page takes 1,400 ms. Its trace and a CPU profile, both taken on that statement, split the time into Postgres queries 350 ms, PDF rendering 800 ms, JSON serialisation 150 ms and audit logging 100 ms. The product owner's target is under 800 ms for the same statement.

Which one change reaches it?

Pick one

Question 2 of 5

A streaming service's catalogue page takes 1,000 ms on a test account. Its trace puts 400 ms in ranking the recommendations and 600 ms in everything else. The goal is 800 ms on that account, by speeding up the ranking alone. Options are rounded to one decimal place.

At least how many times as fast must the ranking become?

Pick one

Question 3 of 5

A ledger's nightly close runs for 40 minutes on one worker: 10 minutes loading the day's bank files, one after another, which cannot be split, then 30 minutes matching entries, which splits evenly across workers. Options are rounded to one decimal place.

With 6 workers, about how many times as fast is the close?

Pick one

Question 4 of 5

ExamHall's grade export takes 1,500 ms for a 300-candidate exam. Two merge requests speed up different parts of it. Measured on that exam, the first alone brings the export to 1,200 ms and the second alone to 900 ms. Options are rounded to the nearest 10 ms.

With both merged, about how long will the export take for the same exam?

Pick one

Question 5 of 5

A payout export took 2,000 ms on the April file: building the CSV 1,200 ms, Postgres 500 ms and the upload to the bank 300 ms. A first change made building the CSV 4 times faster on that file. Each owner now offers one more change for the next sprint.

Which saves the export the most time on the same file?

Pick one

Go deeper

Pro tips
  1. node --cpu-prof app.js writes a .cpuprofile that you open in Chrome DevTools; the flags are stable since Node 22.4.0 and 20.16.0. Profile before you guess, and again after, with the same input. Add --cpu-prof-dir=prof to keep the files together:
    node --cpu-prof --cpu-prof-dir=prof app.js
    When: before choosing what to optimise in a Node service, and after the change (steps 2 and 11). Verify: one .cpuprofile per run in the named directory, each a JSON profile with nodes, samples and timeDeltas; opening it in DevTools: not run. Status: RAN.
Source

No item is keyed on a quoted passage: every key is executed arithmetic, and the passages support the teaching. Quotes are verbatim, with line breaks joined. Valid as of 2026-10-02.

  • Gene M. Amdahl, "Validity of the Single Processor Approach to Achieving Large Scale Computing Capabilities", AFIPS Conference Proceedings vol. 30, 1967, pp. 483–485, as reprinted in IEEE SSCS News, Summer 2007: https://www3.cs.stonybrook.edu/~rezaul/Spring-2012/CSE613/reading/Amdahl-1967.pdf (amdahl-1967.txt, text extracted from the PDF by extract-pdf.mjs). The row's first source. Quoted: housekeeping "accounts for 40% of the executed instructions in production runs"; "In an entirely dedicated special purpose environment this might be reduced by a factor of two"; "Overhead alone would then place an upper limit on throughput of five to seven times the sequential processing rate, even if the housekeeping were done in a separate processor"; "the effort expended on achieving high parallel processing rates is wasted unless it is accompanied by achievements in sequential processing rates of very nearly the same magnitude"; and from the reprint's editors, "Interestingly, it has no equations and only a single figure." The reprint's figure caption is by Justin Rattner (2007) and is not used. Supports steps 4–6 and 8.
  • ACM Digital Library, https://dl.acm.org/doi/10.5555/1241515.1241535: the row's second source, the same paper's catalogue page. It returned 403 (a Cloudflare challenge page) to curl on 2026-10-01 and again on 2026-10-02 (CL-030). Not saved; it supports nothing here.
  • Node.js, Command-line API, --cpu-prof: https://nodejs.org/api/cli.html (node-cli.txt, the v26.10.0 documentation on the date read) and, for this lesson's runtime line, https://nodejs.org/docs/latest-v24.x/api/cli.html (node-cli-v24.txt, v24.21.0). The row's third source. Quoted, as of 2026-10-02: "Starts the V8 CPU profiler on start up, and writes the CPU profile to disk before exit."; "If --cpu-prof-dir is not specified, the generated profile is placed in the current working directory."; "Specify the sampling interval in microseconds for the CPU profiles generated by --cpu-prof. The default is 1000 microseconds."; in the history table, "v22.4.0, v20.16.0" with "The --cpu-prof flags are now stable."
  • Wikipedia, Amdahl's law: https://en.wikipedia.org/wiki/Amdahl%27s_law (wp-amdahl-law.txt, the MediaWiki API plain-text extract). Not a row source; used for the formula's statement only, since the 1967 paper has none. Quoted: "the overall performance improvement gained by optimizing a single part of a system is limited by the fraction of time that the improved part is actually used." Supports steps 5–6.

Checked against the tools and docs as of 2026-10-02.