The average said it was harmless
I added a projectile effect to the skeleton mages and wanted to know what it cost. The answer, after a detour I've written about separately, is: nothing at all, most of the time, and about ten milliseconds whenever you c…
I added a projectile effect to the skeleton mages and wanted to know what it cost. The answer, after a detour I've written about separately, is: nothing at all, most of the time, and about ten milliseconds whenever you can see one.
Those are the same number if you average them. That's the whole story here.
The setup
The frame-cost sweep is a harness that runs nine phases back to back, switching parts of the scene off one at a time, while someone in the headset holds a single view. Each phase gets a five-second GPU capture. Diff the phases and you get a cost per thing.
The phase I cared about switches off the bolt's particle effects while leaving the bolt itself flying, so the difference is the visual and nothing else. It only measures anything while mages are actually casting — which means the wearer has to be standing in a room with casters, holding still, being shot at.
That turned out to be impossible to arrange. The sweep started on a timer twenty seconds after launch, which was fine for every previous session because those were all "stand in the clearing and don't move." Twenty seconds is not enough to leave the hub, cross the portal and find a room with skeletons in it. And standing motionless in front of two casters kills you in well under the three and a half minutes the sweep takes, which changes the scene under every phase after the one that killed you.
So the sweep needed a dev panel first: invincibility, a start button, and teleports. It opens on the right thumbstick. Three rows, each one removing a specific reason the measurement couldn't happen. I'd been treating that as overhead standing between me and the number. It wasn't — it was the measurement becoming possible.
The number that was wrong
Nine phases captured cleanly. Baseline averaged 8.72 ms of GPU time; the phase with the bolt
effects switched off averaged 8.04. Normalise for the GPU clock and that's about 0.73 ms for the effect — roughly 8% of frame time, in a scene with headroom. Harmless. Ship it.
I nearly wrote that down.
The reason I didn't is a note in my own project file saying that app_gpu_ms is a stale plateau for the first two-thirds of a capture, and that averaging the whole thing hides real differences. So I pulled the raw samples instead of the summary.
Every trace opens with five to ten repeats of exactly 1.0167 ms, at a byte-identical
timestamp, followed by five to ten repeats of another constant. Those aren't fast frames. 1.0 ms is what the counter reads when the app isn't rendering at all. In a five-second capture, most of the samples were that.
The real samples, at the end, looked like this:
baseline 10.7 10.9 11.2 10.5 [21.7]
vegetation 10.5 [21.0] 10.8 [17.6] 11.1 9.1 [17.8] 10.2 [21.5] 11.0
msaa2 [14.1] 10.2 [16.0] 10.6 [17.8] 10.7 9.1 [15.4] 9.0 [18.6]
nobolteffects 11.0 11.0 11.0 11.0 10.9 10.5 11.0 11.0 11.1 11.0
Every phase spikes to somewhere between 17 and 22 milliseconds, except the one where the bolt effects are switched off, which is flat. Thirty samples, not one above 11.1.
My frame budget at 72 Hz is 13.89 ms. So the effect isn't costing 0.73 ms of headroom. It's
costing nothing on most frames and blowing the budget by 40% on the frames where a bolt is
visible — which, in a fight with two mages, is a third to a half of them.
Why the average lied
Two reasons, and they compound.
The first is the stale prefix: most of what I averaged wasn't a measurement. The second is that the real signal is bimodal. There are two populations — about 11 ms when nothing is on screen and about 19 when something is — and a mean lands in the gap between them and describes neither. Nothing in that scene ever actually costs 13 ms. The average is a number with no referent.
What makes this the interesting kind of mistake is that the wrong answer was *actionable and
reassuring*. 0.73 ms means "fine, add more effects." The truth means "this drops a frame every time it fires, and don't add another until you've fixed the overdraw." I would have made a real decision on the strength of an artefact.
What I'm keeping
The measurement itself: the bolt is an overdraw problem, a big translucent quad-heavy particle system in a scene that's already spending 90% of its GPU time shading fragments. Particle count, size and shader are the dials.
The method: never quote a whole-capture mean, and look at the distribution before believing any summary statistic. A shape survives noise that a delta doesn't — I can't price the vegetation or the enclosure from this run at all, because a live combat room swings 9 to 21 ms depending on what is happening and the GPU clock moved mid-capture. But spike-versus-flat is far too large to be an artefact of any of that.
And one thing I only noticed by getting it wrong: the sweep has no idea whether anything is
recording it. The captures are taken by a separate script watching the log. I started the sweep without it once, and it logged a perfect, complete, nine-phase run with zero traces on disk — indistinguishable from success. Somebody stood in front of two casters for four minutes for that.