Perf/watt benchmarks in a k8s home cluster

From dtype.org

Perf/watt of a heterogeneous Kubernetes cluster: Apple silicon vs x86 vs Raspberry Pi CM5

Measured performance-per-watt for the nodes of a real, running Kubernetes cluster — Apple M1/M4 Mac minis, low-power x86 boxes, and Raspberry Pi CM5s — with power taken at the wall rather than from spec sheets. The lens is deliberately "what does a container on this node get, and what does the node cost to own", not bare-silicon SPEC. Written for anyone sizing a home or small-office cluster for energy efficiency. Claims are limited to what we directly measured or can cite; inference and open questions are marked as such. The headline score is Geekbench 6 (run 2026-07-24); the power figures come from a companion 7-Zip run (2026-07-14), for a reason explained under Method. The raw method is given so it can be repeated.

Summary

  • Apple M4 wins performance-per-watt by a wide margin — about 2.5× the best x86 on multi-core and ~6× on single-core, and it does so from inside a virtual machine while the x86 boxes run on bare metal.
  • Single-core perf/watt is where x86 collapses. A Raspberry Pi CM5 sipping a few watts beats both x86 nodes outright per watt, because on an x86 desktop the whole package, DRAM and PSU stay awake to run one core.
  • x86 wins absolute throughput, not efficiency. The i7-14700T posts the second-highest multi-core score of any node and nearly the worst multi-core score-per-watt.
  • The CM5 and low-power x86 are tied on multi-core perf/watt once the measurement boundary is corrected for.
  • Geekbench can't measure its own power on this rig — a real limitation, not a footnote — it forced a two-benchmark method. Details under Method.
  • sysbench disagreed with both Geekbench and 7-Zip by up to ~3× on cross-architecture ranking. Anyone comparing ARM and x86 with a single, badly-chosen CPU benchmark is likely publishing a wrong number.
  • The idle floor dominates the result. A node that idles at 17 W has to win hard on throughput to break even against one that idles at 2.6 W.

The cluster under test

All nodes are workers in one k3s cluster (v1.36.2+k3s1), all running the same containerised benchmark. RAM differs across nodes but is irrelevant to a CPU benchmark and was not normalised.

Node CPU Arch Threads Host OS Notes
cx3 Intel i7-14700T x86_64 28 Ubuntu 26.04 "T" 35 W-class part; carries Postgres/Plex/Thanos
cx4 AMD Ryzen 5 5600U x86_64 12 Ubuntu 26.04 Mini-PC, mobile APU
mx3, mx4 Apple M4 arm64 10 Ubuntu 26.04 in a Lima VM on macOS VM gets the whole box (10 cpu / 12 GiB); software-only, no hardware encode
mx1 Apple M1 arm64 8 Fedora Asahi Remix 44 Bare metal — no VM, no macOS; 16 KB pages
x1, x2, x3 Broadcom BCM2712 (Cortex-A76) arm64 4 Ubuntu 26.04 Raspberry Pi CM5, PoE-powered; x2/x3 have 4 GB, x1 16 GB

An eighth node from the earlier 7-Zip run, an Intel i9-13900 desktop (cx1, 32 threads, ATX PSU), has since been powered off precisely because of these measurements — it idled at 25 W, the heaviest floor in the fleet, and the capacity wasn't needed. Its 7-Zip figures are quoted below where relevant but it was not part of the Geekbench run.

Method

Benchmarks

The score of record is Geekbench 6.7.1, the widely-recognised cross-platform CPU benchmark, run from a purpose-built multi-arch container as a Kubernetes Job pinned to each node with nodeName. It runs on every architecture here including the M1's 16 KB-page Fedora Asahi (Geekbench ≥ 6.2.2 supports 16 KB pages). Each node ran the benchmark 2–3 times and we take the median of the single- and multi-core scores.

Two older benchmarks from the 2026-07-14 run remain in play:

  • 7z b (7-Zip 25.01) — -mmt1 / -mmt$(nproc), looped to fill 300 s, score = the "Tot" rating in MIPS. This is the source of the power figures (see below) and a cross-check on Geekbench's ranking.
  • sysbench cpu 1.0.20 — kept only as a cautionary cross-check; it is unreliable across architectures (see "The benchmarks disagree").

Why the power comes from 7-Zip, not Geekbench

This is the central methodological wrinkle. We want score ÷ watts, but on this cluster the two halves can't come from the same benchmark run:

  • Geekbench runs single- and multi-core interleaved in one shot, with no supported way to run just one mode. A run is therefore a single + multi + idle-gap blend of power.
  • The wall meters resolve 60 s and lag a load change by 60–120 s. A duty-cycled ~3-minute Geekbench run simply cannot be decomposed into a clean per-mode power figure at that cadence — and looping it only lengthens the blend, buying a better score median but nothing for power.

So power is measured separately, from the sustained 7-Zip runs: a full 5 minutes of pure single-thread load, then a full 5 minutes of pure all-core load, each long enough for the meter to settle onto a plateau. We pair Geekbench's score with 7-Zip's load watts for the same node and mode, on the assumption that the peak all-core draw is comparable between two CPU-bound benchmarks. It is an approximation, and we flag it as one.

Measuring the power

Every node is individually metered, polled into Prometheus/Thanos, so a benchmark's power is a time-range query over the exact epochs the benchmark logged.

  • x86 and Mac nodes — a UniFi smart PDU, one outlet each: true AC at the wall, including each machine's own PSU losses.
  • CM5 nodes — PoE ports on a UniFi switch: DC delivered at the port, which excludes the switch PSU's AC→DC conversion loss.

These are not the same boundary, and that matters — see Caveats.

Three practical findings about the instrument, each of which would have silently corrupted the results:

  1. The meter's real cadence is 60 s, not the ~2 min we had assumed.
  2. The PDU lags a load change by ~60–120 s. Every multi-thread window opened with one or two samples still reporting the previous test's power. A 5-minute window survives this (take the last 3 samples); a 2–3 minute window would have consisted almost entirely of stale pre-load readings and produced plausible-looking, entirely wrong numbers. This is also why a bursty Geekbench run can't be power-metered directly.
  3. Do not use a Prometheus query_range with a step finer than the scrape interval. It carries the last sample forward to fill empty steps, so the head of every window replays the previous test's power as if it were real data. Query raw samples with a range selector instead.

Baseline (idle) power, and why it is included

The headline metric is score ÷ total wall watts, with the idle floor included, not subtracted. An always-on cluster node burns its floor 24/7 whether or not it is computing, so that draw is a real cost of owning it. We deliberately did not normalise it away.

Idle was taken as the 10th percentile over a verified-quiet window with the cluster's batch work stopped. This matters more than it sounds: a naive "10th percentile over 24 h" gave 48 W for cx3, because a transcoding job had been running for most of the day. A percentile is only an idle estimate if the machine was actually idle for most of the window.

Node Idle (W) Boundary
cx3 (i7-14700T) 17.2 PDU (AC)
cx4 (Ryzen 5600U) 9.9 PDU (AC)
mx1 (M1) 5.8 PDU (AC)
mx3 (M4) 2.9 PDU (AC)
mx4 (M4) 2.6 PDU (AC)
x1 (CM5) 2.7 PoE (DC)

The spread here is the story of the whole article: an M4 Mac mini idles at about the same power as a Raspberry Pi, despite several times the peak performance, while the x86 nodes idle at 4–7× either.

Results

Geekbench 6, multi-core — the headline

Score ÷ total load watts (idle included). Higher score-per-watt is better.

Node CPU Geekbench 6 multi Idle W Load W Score/W Boundary
mx3 Apple M4 14,448 2.9 24.7 585 PDU
mx4 Apple M4 14,270 2.6 28.2 506 PDU
mx1 Apple M1 8,423 5.8 18.7 450 PDU
cx3 i7-14700T 12,928 17.2 54.2 239 PDU
x1 CM5 (A76) 1,538† 2.7 7.2 214†* PoE
cx4 Ryzen 5 5600U 6,310 9.9 32.7 193 PDU

* PoE figure — not directly comparable to the PDU rows; see Caveats.
x1's multi-core score is corrected upward for contamination — it was the only CM5 that could run the benchmark, and it was busy. See "We were forced to benchmark the busy machine".

Read it as three tiers:

  • Apple M4 — ~2.5× the best x86 per watt. And it earns that through a virtualization layer, software-only.
  • M1 close behind, still roughly double any x86.
  • CM5 and low-power x86 — tied, once the boundary is corrected: x1's ~214 (corrected) lands right on cx3 and above cx4.
  • The i7 is the throughput node and an efficiency loser: 12,928 is the second-highest score in the table, at 54 W to get it.

Geekbench 6, single-core — where x86 falls apart

Node CPU Geekbench 6 single Load W Score/W
mx3 Apple M4 3,577 9.5 377
mx4 Apple M4 3,517 9.8 359
mx1 Apple M1 2,227 10.8 206
x1 CM5 (A76) 810 5.5 147*
cx4 Ryzen 5 5600U 1,803 27.8 65
cx3 i7-14700T 2,244 37.6 60

The x86 parts have respectable single-core scores — the i7's 2,244 beats the CM5's 810 by nearly 3×. But it burns 37.6 W to run one core against the CM5's 5.5 W, so per watt it lands below the Raspberry Pi. On exactly the kind of latency-sensitive, lightly-threaded work a home cluster spends most of its life doing, a big x86 desktop is the least efficient thing you can own: the package, the DRAM and the PSU all stay awake to feed a single thread.

7-Zip corroborates, and provides the watts

The 7-Zip all-core run (the power source) ranks the nodes the same way Geekbench does — Apple M4 far ahead, then M1, then CM5 tied with low-power x86 — which is the cross-check that lets us trust the Geekbench numbers. For the record, the earlier 7-Zip headline (MIPS per watt, all cores) put the two M4 nodes at ~3,024 and ~2,641, the M1 at 2,269, and the CM5 and Ryzen bunched at ~1,480–1,680. The now-retired i9-13900 topped the throughput chart at 144,572 MIPS — nearly double any other node — while drawing 100.9 W to do it, which is what got it switched off.

The benchmarks disagree, and one of them is lying

The most portable lesson here. Three respected CPU benchmarks, same machines. Geekbench 6 and 7-Zip agree on the cross-architecture ranking and roughly on magnitude; sysbench is the outlier:

How much faster is the Apple M4's single core than the Intel i7-14700T's? Three benchmarks, same two machines, same run:

Single-core, M4 (mx4) vs i7-14700T (cx3) Ratio (M4 ÷ i7)
sysbench (13,519 vs 3,357 events/s) 4.0×
7-Zip (9,529 vs 6,972 MIPS) 1.4×
Geekbench 6 (3,517 vs 2,244) 1.6×

sysbench claims the M4 is four times the i7; the two real-workload benchmarks both say roughly one-and-a-half. [https://blog.cloud-mercato.com/why-you-should-use-sysbench/ sysbench's CPU test is a simplistic prime-number benchmark built on 64-bit integer arithmetic, and is heavily dependent on 64-bit division] — written with database-like integer workloads in mind, not as a general CPU metric. Integer-division throughput is one of the most microarchitecture-variable operations there is, so a division-bound loop is close to a worst case for cross-architecture comparison. Geekbench 6 (a broad suite of real-world workloads) and 7-Zip (a compression workload) agree with each other and disagree with sysbench by ~3×. Treat a single division-bound microbenchmark as unreliable for ARM-vs-x86. The rankings still agree across all three (M4 > M1 > CM5 ≈ x86) — it is the magnitudes sysbench gets wrong.

Two things we learned about measuring, not about CPUs

We were forced to benchmark the busy machine

The rule is "never benchmark the machine you are working from" — x1 hosts the operator tooling that orchestrated this very run. In the 7-Zip round we obeyed it by using an identical, idle CM5 (x2) as the datapoint. Geekbench took that option away: its working set OOM-kills a 4 GB CM5 (the image buffers in its photo/ray-tracing workloads exceed what etcd + the OS leave free), so x2 and x3 couldn't run it at all. The only CM5 that could was x1 — the 16 GB board — which is also the busy one.

So we ran it anyway and corrected for it, using what the 7-Zip round had already quantified. That round had A/B'd the same benchmark on busy x1 vs clean, identical x2:

Test x1 (busy) x2 (clean) x2 advantage
Single-thread 0.0% (one core of four, no contention)
7z multi (all cores) 12,428 14,194 14.2%

Single-thread is immune — one core of four, the other three absorb the noise — so x1's Geekbench single-core score is taken as-is. Multi-core is where a busy node degrades, and not uniformly: it degrades where the benchmark is sensitive. We therefore lift x1's Geekbench multi-core score by the 14.2% the 7-Zip A/B established (1,538 → ~1,756, score/W ~244). The control test on this run confirmed the co-resident containers were quiet (~0.2 cores between them); the real thief was the k3s control plane — apiserver and etcd — which runs as system daemons and is invisible to per-container CPU accounting, costing roughly 40% of one core throughout the run.

Kubernetes makes a small node's power visibly lumpy

The CM5 nodes' power wandered by ±1 W at idle — enough to matter when the entire all-core load delta is only ~4 W. Our first instinct was to blame the meter. That was wrong, and the control test is worth copying:

Device Mean W Stdev W What it is
usl-gw 1.74 0.05 Dumb constant PoE load
u1, u2 2.75, 2.51 0.17, 0.21 CM5s, but not running k3s
x1, x2, x3 5.14, 3.81, 3.30 1.69, 0.97, 0.76 CM5s running k3s + etcd

The meter resolves 0.05 W. Two identical CM5 boards that are not cluster members sit at 0.17–0.21 W of variation; the cluster members show 10–30× that. The wobble is real power: k3s and etcd make a small ARM node's draw genuinely bursty at idle. There is no stable "idle floor" to measure on such a node, only a distribution — which is why a percentile over a verified-quiet window, not a spot reading, is the only honest baseline.

Caveats

  • Score and power come from different benchmarks. Geekbench provides the score, 7-Zip the watts (see Method). We assume comparable peak all-core draw between two CPU-bound benchmarks; we did not, and on this rig could not, measure Geekbench's own per-mode power.
  • Mixed measurement boundaries. CM5s are metered at the PoE port (DC delivered, excluding the switch PSU's conversion loss); everything else at the PDU (AC at the wall). The switch draws 105.1 W AC to deliver 77.2 W of PoE across all ports; allowing 15–25 W for the switch's own electronics puts its PSU at ~86–96% efficient, i.e. a ×1.04–×1.17 correction that raises the CM5's true watts and lowers its score/W — landing x1 squarely with the low-power x86. So "the CM5 beats low-power x86" is not supportable; "they are tied" is. The M4's ~2.5× lead is far outside this correction and is unaffected.
  • x1's multi-core is a corrected estimate, not a clean measurement — the only CM5 with enough RAM to run Geekbench was the busy one. The correction factor is borrowed from a 7-Zip A/B on identical hardware; the single-core figure is a clean measurement.
  • The Lima VM tax is unquantified. The M4 nodes ran inside a VM, software-only, and still won. We did not measure the native-vs-virtualised delta, so the M4's true native figure can only be better than reported. The ~2.5× is a floor.
  • CM5 precision is ~±6%, adequate for tier placement, not for splitting a tie.
  • "Idle" is as-deployed, not bare. cx3 permanently hosts Postgres, Plex, Thanos and a Minecraft server and never goes below 12.4 W in 30 days. Its idle figure includes that resident work — deliberate (what the node actually costs to own), but not a board-level idle spec.
  • Few trials. Geekbench scores are a 2–3-run median; the 7-Zip/power figures are a single verified-quiet afternoon. No confidence intervals beyond the within-window spread.

Reproducing this

Geekbench 6 in "tryout" mode does not print scores to the console — it uploads each result to the Geekbench Browser and returns a URL — so the harness captures the URL per run and fetches the scores back from the result page. The benchmark image is Debian-based (a shell and CA certificates, so the upload works, unlike the common distroless Geekbench images) and multi-arch (amd64 + arm64). Power is the companion 7-Zip run: debian:trixie-slim plus apt-get install 7zip (the binary is 7z), logging BENCH_START/BENCH_END epochs around each test, with power taken as a raw range query over exactly those epochs, last 3 samples. Run everything when the cluster is genuinely idle, one node per metered domain, and remember that a 4 GB Raspberry Pi CM5 will OOM under Geekbench.