CPU-Bound vs GPU-Bound

A frame is produced by two different machines — a few fast sequential CPU cores and thousands of parallel GPU cores. Frame time ≈ max(CPU half, GPU half): knowing which half is the wall decides which optimization helps.

Core Idea

Every frame is produced by two different machines working together. The CPU decides what to draw and where; the GPU actually paints it. The frame is only as fast as the slower half — drag either bar past the other and watch the fix for one become useless:

You are CPU-bound when the CPU half is the wall, GPU-bound when the GPU half is. The tool above measures both halves of a real scene live: drag the levers and watch the bound move.

Two Different Shapes of Machine

CPU GPU
Cores a few, very fast thousands, individually slow
Built for sequential, branchy logic the same tiny program on millions of items
In a frame game logic, then one draw-this order per object transform every vertex, shade every pixel
In the browser everything on the single JS main thread vertex → rasterize → fragment → post passes

They are not “fast” and “faster” — they are different shapes. Give each the work it’s shaped for.

The Deadline

60 fps means each frame must finish in 16.67 ms. With vsync there is no partial credit: a frame that misses slips to the next refresh, snapping straight toward 33 ms / 30 fps — there is no in-between. The meter above draws the deadline as a line; push a lever past it and watch the bars jump to the next plateau rather than climb smoothly.

The CPU’s Real Cost: Orchestration, Not Computation

The logic itself is almost never the wall — a modern CPU does billions of operations per second, so updating hundreds of animated objects costs microseconds. The expensive part is talking to the GPU: every draw call is a trip across the JS → driver → GPU boundary, validated and serialized on one thread.

The CPU is a manager; the GPU is a factory floor of a thousand workers who finish instantly and stand idle, waiting for the next order. Watch the floor — then switch to one batched order:

The manager is the bottleneck, not the work. The fix is fewer, bigger orders: draw-call batching (instancing).

Which Half Are You In?

Observation Diagnosis
Main thread busy the whole frame (DevTools Performance) CPU-bound
Main thread short, frame still long GPU-bound
Cap DPR → frame time drops a lot you were fill-bound
Cap DPR → barely moves CPU- or vertex-bound

The readout in the tool above shows the same split: CPU half (submit) is the real time spent issuing commands on the JS thread, measured against the whole frame.

Why Mobile Falls Off a Cliff

The two costs that scale worst from desktop to mobile are per-draw-call overhead (weaker cores, heavier drivers) and fill rate — triangle count usually survives the trip. A scene comfortably at 38 fps on a desktop can collapse to 13 fps on a mid-range phone. A healthy mobile budget is roughly 100–200 draw calls.

You don’t optimize what’s slow on your dev machine — you optimize what will be slow on the target. If the tool above stays green with every lever maxed, that is this lesson, live.

Connections

CPU-bound is almost always a draw-call problem → draw-call batching (instancing, geometry merging). GPU-bound is a fill/vertex problem → mobile optimization (DPR cap, cheaper materials, LOD). The split mirrors the hardware stages of the GPU pipeline. Frame stutter (spikes, not steady cost) is often garbage collection — a different disease with a different cure.