Performance, measured
Every figure on this page has a date and a method. Production figures come from shotcup.dev on its real hosts; figures from a development machine say so. Where the engine is slower than a V8 runtime, that is here too.
A run on shotcup.dev
Server time is the API's own Server-Timing total: the key check, admission, the run in a sandbox and the stored result. The caller's round trip adds the network to Frankfurt and back.
89.5ms
Run on shotcup.dev, server time, p50
A small program through POST /api/v1/runs, from the request reaching the API to the answer leaving it: key check, admission, the sandbox run and its result.
Production. Vercel function in Frankfurt (fra1) to the sandbox machine on Fly in Frankfurt; three passes of 50 runs after 10 warm-ups, pooled. .
128.2ms
Run on shotcup.dev, server time, p95
The same runs as the p50: the time below which 95 of 100 runs answered.
Production. Vercel function in Frankfurt (fra1) to the sandbox machine on Fly in Frankfurt; three passes of 50 runs after 10 warm-ups, pooled. .
136.9ms
Round trip seen by a caller in London, p50
The same kind of run timed by the caller: network to Frankfurt and back included.
Production. A client in London to shotcup.dev, Vercel function in Frankfurt (fra1) to the sandbox machine on Fly in Frankfurt; 30 runs (p95 243.9 ms). .
Target, not yet met. We hold the server time to a p50 of 85 ms and a p95 of 150 ms. The p95 is inside its target; the p50 of 89.5 ms is not. Most of it is admission: one database call from Frankfurt that decides every quota for the run.
Launch templates on the production host
The time each template's run spends in the sandbox (the daemon's wall time), measured through the public API on shotcup.dev: 20 runs after 3 warm-ups each, on a Fly performance-1x machine in Frankfurt.
| Template | p50 | p95 | Measured |
|---|---|---|---|
| Chart with tabular figurescanvas-chart | 36 ms | 41.3 ms | |
| Canvas and image cardcanvas-image | 55.9 ms | 58.8 ms | |
| Card with colour emojicanvas-emoji-card | 64.1 ms | 110.8 ms | |
| Windmill with Canvas 2D pathscanvas-paths | 362 ms | 394.7 ms |
Waking a suspended machine
Sandbox machines suspend when idle, and the first run after that waits for the machine to resume. Runs on a machine that is awake do not pay this.
0.7-2.3s
First run after the machine was suspended
Client time of the first run after Fly suspended an idle sandbox machine (server time 0.5-2.0 s).
Production. Vercel function in Frankfurt (fra1) to the sandbox machine on Fly in Frankfurt; five samples; how fast Fly resumes a machine varies. .
Inside the engine
What isolation and tool calls cost inside the runtime, measured on a development MacBook (Apple M1 Max). The production machines' cores are slower, so read these as the engine's own costs, not as hosted latency.
2.07ms
A fresh engine for a run, p50
Creating a new engine with its runtime and sandbox for one run (p95 2.60 ms, n = 10).
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, in-process. .
2.1ms
A nested sandbox inside a run, p50
A run starting a child sandbox with its own engine and limits (the nested-run verifier).
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, shotcupd daemon, in-engine time. .
2.87ms
A trivial run through the daemon, p50
console.log through the daemon's protocol to a pooled worker and back (p95 3.12 ms, n = 1,000).
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, shotcupd daemon, one connection, 4 workers. .
2.5-3µs
A synchronous tool call
A script calling a host function and getting its value back, including the JavaScript loop around it.
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, runtime spike, 100,000 calls. .
20-37µs
An awaited tool call
A promise-returning host function, resolved and awaited by the script.
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, runtime spike, 2,000 sequential awaits. .
2,584-2,688runs/s
Trivial runs per second, one daemon
Sustained trivial runs with 64 in flight, 0 failures over 20 s (p50 24-26 ms under that load).
Development machine, not production. MacBook Pro, Apple M1 Max, macOS arm64, 10 CPUs, shotcupd with 8 workers (throughput verifier). .
Where the engine is slower
GocciaScript is a bytecode interpreter, and a V8 runtime with a JIT is faster at raw compute. The same workloads as whole processes on one MacBook, GocciaScript against Bun, p50 in ms. The gap is small for short glue code and large for heavy loops; upstream engine work keeps narrowing it.
- GocciaScript (Shotcup's engine)
- Bun 1.4.0
| Workload | p50, GocciaScript then Bun | Ratio |
|---|---|---|
| Hello world | 13.4 ms9.1 ms | 1.5x |
| Hook policy decision | 21 ms16.8 ms | 1.3x |
| Browser action planner | 23.4 ms15.9 ms | 1.5x |
| Fetch fixture and diff | 59.3 ms17.5 ms | 3.4x |
| Aggregate 2,000 rows | 120 ms18.1 ms | 6.6x |
| Code-mode orchestrator | 198 ms23.9 ms | 8.3x |
MacBook Pro, Apple M1 Max, macOS arm64; GocciaScript (bytecode, engine 5ef92216) and Bun 1.4.0 as processes, call to exit, 20 runs each. .
How these numbers are taken
p50 is the median run and p95 the run that 95 of 100 finished within. Production figures are taken against shotcup.dev with the warm-up runs discarded; the sample size is given with each. The dates show when a figure was last measured.
How Shotcup compares with other sandboxes on model and price: the comparison.