Published by Sean Carr
Our Culling Number Was Right and Still Misleading
A 0.104 ms frustum-cull figure timed the kernel only; the maintenance work that must run beside it was 48x to 64x larger.
At a Glance
Maintenance vs cull, live scene
47.9x
Live scene (2,388 items), static: maintenance 0.0863 ms vs cull 0.0018 ms; total 0.0882 ms. Maintenance-to-cull ratio 47.9x; total-to-cull 49.0x.
Maintenance vs cull, 100,000 items
64.4x
100,000 items: cull only 0.0824 ms; with the static maintenance path the engine actually runs, 5.3068 ms total (64.4x).
Cull kernel, 100,000 boxes
0.104 ms
Cull kernel, 100,000 boxes, cold cache: 0.3451 ms before a data-layout refactor to 0.104 ms after (about -69%), against a 0.50 ms budget line.
Live scene, 10% moving
44.0%
Live scene with 10% of objects moving (239 objects): 7.3314 ms total, 44.0% of a 16.67 ms frame.
| Telemetry Metric | Measured Value | Context & Observability |
|---|---|---|
| Maintenance vs cull, live scene | 47.9x | Live scene (2,388 items), static: maintenance 0.0863 ms vs cull 0.0018 ms; total 0.0882 ms. Maintenance-to-cull ratio 47.9x; total-to-cull 49.0x. |
| Maintenance vs cull, 100,000 items | 64.4x | 100,000 items: cull only 0.0824 ms; with the static maintenance path the engine actually runs, 5.3068 ms total (64.4x). |
| Cull kernel, 100,000 boxes | 0.104 ms | Cull kernel, 100,000 boxes, cold cache: 0.3451 ms before a data-layout refactor to 0.104 ms after (about -69%), against a 0.50 ms budget line. |
| Live scene, 10% moving | 44.0% | Live scene with 10% of objects moving (239 objects): 7.3314 ms total, 44.0% of a 16.67 ms frame. |
| Hot-path working set | -56% | Hot-path working set: 5.436 MB to 2.384 MB (-56%). |
| Correctness vs oracle | 0.04% | Correctness: false negatives vs oracle 0; false positives 1 (0.04%); hot-path allocations 0 over 500 frames. |
| Bare visit loop, no memo | 0.1032 ms | A bare visit loop with no memo and a cheap early-out still cost 0.1032 ms, more than the 0.0824 ms cull it feeds. |
| 100,000 items, all moving | 3082.4516 ms | 100,000 items, all moving, palm-class meshes: 3082.4516 ms for one frame. |
| Live memo hit rate | 97.8% | Live memo hit rate on the shipped executable: at least 97.8% (derived bound, not a direct count). |
Published by Sean Carr
View & print executive brief version →Executive Summary & Core Finding
We published an internal verification annex showing the CPU frustum culler handles 100,000 bounding boxes in 0.104 ms. The same day, a residency stress test showed the per-frame bounds-maintenance pass that must run alongside the cull costs 47.9x the cull on the live scene and 64.4x at 100,000 objects. Every number was correctly measured; the error was that the timer boundary was not part of the record.
Method
Data collected Jul 31, 2026; written up October 2026.
All measurements on July 31, 2026, on a development workstation (AMD Ryzen 7 9800X3D, 96 MB L3, DDR5).
The cull kernel was timed with median-of-500 passes under a cold-cache protocol (a 128 MB write sweep before each pass, outside the timed region), checked against an independent brute-force oracle.
The system-scope figures came from a separate stress harness reporting median-of-200 frames (median-of-100 or -60 for the heaviest variants), with maintenance timed on a transcription of the shipped resolver rather than the shipped backend.
Results were not reproduced on the low-end validation PC.
What We Measured
Cull kernel timing versus per-frame system cost
Maintenance vs cull, live scene
47.9x
Live scene (2,388 items), static: maintenance 0.0863 ms vs cull 0.0018 ms; total 0.0882 ms. Maintenance-to-cull ratio 47.9x; total-to-cull 49.0x.
Maintenance vs cull, 100,000 items
64.4x
100,000 items: cull only 0.0824 ms; with the static maintenance path the engine actually runs, 5.3068 ms total (64.4x).
Cull kernel, 100,000 boxes
0.104 ms
Cull kernel, 100,000 boxes, cold cache: 0.3451 ms before a data-layout refactor to 0.104 ms after (about -69%), against a 0.50 ms budget line.
Live scene, 10% moving
44.0%
Live scene with 10% of objects moving (239 objects): 7.3314 ms total, 44.0% of a 16.67 ms frame.
Hot-path working set
-56%
Hot-path working set: 5.436 MB to 2.384 MB (-56%).
Correctness vs oracle
0.04%
Correctness: false negatives vs oracle 0; false positives 1 (0.04%); hot-path allocations 0 over 500 frames.
Bare visit loop, no memo
0.1032 ms
A bare visit loop with no memo and a cheap early-out still cost 0.1032 ms, more than the 0.0824 ms cull it feeds.
100,000 items, all moving
3082.4516 ms
100,000 items, all moving, palm-class meshes: 3082.4516 ms for one frame.
Live memo hit rate
97.8%
Live memo hit rate on the shipped executable: at least 97.8% (derived bound, not a direct count).
What We Think It Suggests
- A kernel benchmark and a per-frame system cost are different claims, and a complete measurement record (value, statistic, date, hardware, commit, run condition) can still mislead if it does not say where the timer starts and stops. We added scope, meaning what is inside and outside the timed region, as a required field.
- The data-layout refactor appears to have been a real win for the cull itself. The per-frame cost of visibility, though, was dominated by visiting every item to check whether its bounds were still valid, and that cost grows steeply once objects move.
Limits
- Single development-class machine with a very large L3 cache; the low-end validation fixture has not been measured, and the cold-cache penalty there is plausibly larger.
- The maintenance figures time a faithful transcription of the shipped resolver, not the shipped render backend, which is weaker evidence than the kernel figures.
- A later fix landed in the maintenance path after these measurements, so the system-scope numbers describe the code at that commit and are now stale.
Detailed Analysis & Field Observations
We published an internal verification annex showing the CPU frustum culler handles 100,000 bounding boxes in 0.104 ms. The same day, a residency stress test showed the per-frame bounds-maintenance pass that must run alongside the cull costs 47.9x the cull on the live scene and 64.4x at 100,000 objects. Every number was correctly measured; the error was that the timer boundary was not part of the record.
Data collected Jul 31, 2026; written up October 2026.