TL;DR — Published benchmarks tell you who's fast in general; only your workload tells you who's fast for you. This is a repeatable playbook: define the workload and the metric that matters, build a representative test, control every variable, run it many times on candidate instances, and rank by performance-per-dollar at your prices. The output is a defensible decision, not a vibe.
Why generic benchmarks aren't enough
A leaderboard says "Axion beats Graviton4 on CoreMark." Useful — but your service might be memory-bound, I/O-bound, or pinned by a single-threaded section. The only benchmark that predicts your bill is the one that runs your code under your load. Treat published numbers (Parts 2–3) as a shortlist generator, then validate the shortlist yourself.
Step 1 — Define the workload and the metric
Before touching a cloud console, write down two things:
- The workload profile. Is it CPU-bound (compute), memory-bound (caches, analytics), I/O-bound (databases), or network-bound? Single-threaded latency or parallel throughput? Steady or spiky?
- The metric and the SLO. Pick one primary metric tied to a business goal: p99 request latency under X ms, builds per hour, tokens/sec, cost per million operations. If you can't name the metric, you can't benchmark.
Fig 1 — The loop. Skip a box and the result stops being trustworthy.
Step 2 — Build a representative test
Three tiers, weakest to strongest signal:
- Synthetic to characterize the hardware — quick sanity check and to explain why later.
- Component tests of your bottleneck — e.g.
fioif you're I/O-bound,sysbench oltpif database-bound. - Application replay — the gold standard. Replay real traffic or a captured production load against your actual service.
Quick-start commands for the common tools (install via your package manager):
# CPU — single & multi-thread
sysbench cpu --threads=$(nproc) --time=60 run
# Memory bandwidth / latency
sysbench memory --memory-block-size=1M --memory-total-size=100G run
# (or the STREAM binary for canonical bandwidth)
# Disk I/O — random read latency
fio --name=randread --rw=randread --bs=4k --size=4G --runtime=60 --time_based
# HTTP service load — throughput + p99 latency
wrk -t8 -c200 -d60s --latency https://your-service.internal/health
# k6 for scripted, realistic user journeys
k6 run load-test.js
For builds/CI, just time the real thing under controlled conditions: time make -j$(nproc), time cargo build --release, time mvn -T1C package.
Step 3 — Control every variable
This is where most internal benchmarks quietly become wrong. Change one thing at a time. Hold constant:
- Same region / AZ (and ideally placement), same OS image, kernel, and tool versions.
- Same compiler flags and runtime config (especially JVM/GC settings — they differ by arch).
- Comparable instance size — and normalize ARM/x86/OCPU thread counts so you compare equal work, not equal labels.
- Avoid burstable instances (t-class, fractional OCPU) for sustained tests — burst credits make the first minutes lie.
vmstat 1 / mpstat for the steal column — non-zero steal means your number is about the host, not the chip.Step 4 — Run it (and automate so it's repeatable)
Make the run reproducible from day one — you'll re-run it every time prices or instance families change. A minimal pattern:
# spin candidates with IaC so configs are identical
terraform apply -var 'instance=m8a.2xlarge' # AWS Turin
terraform apply -var 'instance=E6.Flex' # OCI Turin
# one script, run on each, emits JSON
./bench.sh | tee results-$INSTANCE.json
# tear down immediately — benchmarking idle instances burns money
terraform destroy
Capture per run: instance type, region, OS/kernel, tool versions, every iteration's result, and the on-demand price you actually pay (your negotiated/committed rate, not list).
Step 5 — Rank by performance per dollar
The decision metric, every time:
perf_per_dollar = throughput / hourly_price
# or for latency-bound work, the inverse cost:
cost_per_million_ops = (hourly_price / 3600) / ops_per_sec * 1e6
Build a small table: candidate × (your metric, hourly price, perf-per-dollar, monthly cost at your real utilization). The winner is rarely the fastest box — it's the best ratio at a commitment model you can actually sign (on-demand vs reserved vs spot; see Part 3).
| Candidate | Your metric | $/hr | Perf/$ | Verdict |
|---|---|---|---|---|
| Fast/expensive | 100 | 0.40 | 250 | only if metric is sacred |
| Balanced | 92 | 0.22 | 418 | usually wins |
| Cheap/slow | 70 | 0.14 | 500 | wins if SLO still met |
Note the cheap/slow box has the best perf-per-dollar — but only choose it if its absolute metric (70) still clears your SLO. Perf-per-dollar ranks; the SLO is a hard gate.
Step 6 — Re-run on a schedule
This is not a one-time exercise. New instance families ship quarterly, prices move, and your workload evolves. Wire the benchmark into CI (or a monthly cron) so a regression — or a newly cheaper option — surfaces automatically instead of being discovered in the bill.
The checklist
- ☐ Named one primary metric tied to an SLO
- ☐ Test replays a representative (ideally real) load
- ☐ One variable changed at a time; OS/kernel/flags fixed
- ☐ Non-burstable instances; warm-up discarded
- ☐ 5–10 runs; median + spread reported; steal checked
- ☐ IaC-reproducible; instances torn down after
- ☐ Ranked by perf-per-dollar at your prices, SLO as gate
- ☐ Scheduled to re-run as families/prices change
Do this and "which cloud should we use" stops being an argument and becomes a number with a date on it. That's the whole point of the series — from the vocabulary through the 2026 landscape and the AMD value duel to a method you own.
References & tools
- sysbench — scriptable CPU / memory / OLTP benchmark.
- fio — flexible I/O tester for storage.
- wrk / k6 — HTTP load & latency testing.
- STREAM — canonical memory-bandwidth benchmark.
- stress-ng — stress and micro-benchmark a huge range of subsystems.
Methodology playbook, June 2026. Commands are starting points — adapt sizes and durations to your workload and budget.