Put HST lower in the stack as a user-space runtime layer inside a fully sandboxed Linux VM, then measure whether sparse workloads can be transparently routed through it — instead of being called by hand from one demo.
perf statbaseline_csc — exact CSC reuse-delta every step. The honest sparse baseline.
library_hst — rebuild an HST schedule every step, then apply. Naive "call HST by hand": pays schedule-build cost on every update.
runtime_hst — a node that caches schedules keyed by the dirty-tile-set and gates by density. The caller just submit()s updates; the node decides HST-vs-CSC. This is the runtime layer.
| dirty pattern | density | baseline_csc | library_hst | runtime_hst | runtime / library |
|---|---|---|---|---|---|
| scattered64 | 0.04 | 31.8 ms | 1175.8 ms | 31.2 ms | 37.7× |
| one_tile32 | 1.00 | 25.0 ms | 65.2 ms | 28.5 ms | 2.29× |
| four_tiles32 | 1.00 | 42.1 ms | 169.0 ms | 52.8 ms | 3.20× |
| eight_tiles32 | 1.00 | 58.7 ms | 281.7 ms | 80.8 ms | 3.49× |
The runtime layer recovers ~all of CSC's performance (it routes scattered updates away from HST) while running 2.3–37.7× faster than naive library-HST calls. The schedule cache is the whole point of putting HST in the stack as a runtime instead of a manual call.
Same workload, run inside the isolated guest — the pattern holds (build cost varies with guest scheduling):
| dirty pattern | baseline_csc | library_hst | runtime_hst | runtime / library |
|---|---|---|---|---|
| scattered64 | 31.8 ms | 1579.5 ms | 35.5 ms | 44.6× |
| one_tile32 | 24.8 ms | 77.2 ms | 33.0 ms | 2.34× |
| four_tiles32 | 45.9 ms | 230.5 ms | 65.2 ms | 3.54× |
| eight_tiles32 | 64.2 ms | 371.0 ms | 94.3 ms | 3.94× |
<not supported> — Apple Virtualization.framework does not expose the ARM PMU to the guest. That's the honest ceiling of profiling inside this sandbox; hardware counters need bare-metal Linux.At batch=1 the tile kernel's per-row overhead isn't amortized, so HST loses. Widen the batch (process B state vectors per step) and it crosses over. eight_tiles32, fill=0.60, in-process:
| batch B | csc | hst (in-proc) | hst / csc | verdict |
|---|---|---|---|---|
| 1 | 40.1 ms | 46.5 ms | 0.86× | HST loses |
| 2 | 57.2 ms | 53.5 ms | 1.07× | crossover |
| 4 | 108.0 ms | 82.2 ms | 1.31× | HST wins |
| 8 | 204.2 ms | 138.5 ms | 1.47× | HST wins |
| 16 | 365.2 ms | 227.8 ms | 1.60× | HST wins |
The win grows with batch size, with the number of dirty tiles (one_tile32 tops out ~1.1×; eight_tiles32 reaches 1.6×), and with fill. This scalar kernel leaves more on the table — explicit SIMD across the batch lane is the next step toward the ~2.5× ceiling. Direction: HST needs B≥2 to break even, B≥4 to matter, and wide batches + dense tile-local updates to shine.
The in-process runtime just reads and writes the caller's arrays. Put HST behind a boundary (RPC / REST / separate process) and every step must marshal the dirty input across and the touched output back. Below is that marshalling floor — real memcpy in + out, the cheapest any such design can be. Real IPC adds syscalls and scheduling on top.
| batch B | hst in-proc / csc | hst call-home / csc | the tax |
|---|---|---|---|
| 1 | 0.86× | 0.74× | a loss becomes a bigger loss |
| 4 | 1.31× | 1.13× | −14% of the win |
| 16 | 1.60× | 1.49× | −7% of the win |
Marshalling hurts most exactly where HST is weakest (small batches — copy dominates compute). The takeaway: the win only exists in-process. A version of HST that "calls home" every step cannot realize it; the boundary has to be crossed rarely (batch the work) or not at all (link it in).
In-process only. A daemon / IPC / REST front destroys the sub-millisecond win. The "node" is a linked library, not a service.
Batch=1 doesn't beat CSC. HST loses at B=1 and needs B≥2 to break even (measured above); the win widens with batch size, dirty-tile count, and fill, toward a ~2.5× ceiling with SIMD.
User-space only. No BIOS, kernel drivers, bootloader, or host process injection — matches the tasking guardrails.