Skip to content

Benchmarks

Measured per-operation cost of the kernel's hot paths (token buckets, hooks, the Bus, signals, the scheduler and Serde) and what the numbers mean for a frame budget.

The kernel’s hot paths are measured with Bench.runAll over the modules in ReplicatedStorage.ChloeKernel.Benches. These are microbenchmarks that time one call in a tight loop. Performance covers what the framework costs at idle and under load, and the limits it enforces.

Operation Throughput p50 per op
TokenBucket:take 17.0M ops/s 58 ns
Hooks:fire (3-validator chain) 6.6M ops/s 148 ns
Bus:publish (exact topic) 5.0M ops/s 198 ns
Signal:fire (1 handler) 4.5M ops/s 203 ns
Scheduler schedule and step (MicroProfiler zones off) 2.2M ops/s 354 ns
Scheduler schedule and step (zones on, the Studio default) 2.1M ops/s 388 ns
Serde schema encode (3 fixed fields) 3.5M ops/s 285 ns
Serde schema decode (3 fixed fields) 2.7M ops/s 367 ns
Serde struct delta encode (2 fields) 3.7M ops/s 270 ns
Serde struct delta decode (2 fields) 3.4M ops/s 293 ns
  • A client payload’s validation (a per-channel token bucket plus a three-validator hook chain) costs about 200 ns, well under a microsecond. Rejecting garbage is cheap, and there is no performance argument for skipping validation. Decode cost depends on the schema; see Serde and Packet and wire types.
  • The hot-task profiler attributes each scheduled callback’s time to the script:line where the function was defined. Origins are cached by function identity, so steady-state attribution is one table lookup; the profiler fix that produced this table took schedule-and-step from 2,848 ns to 354 ns, about 33 ns over the 321 ns baseline without profiling. Since 0.8.0, Kernel.boot({ ProfileTasks = false }) skips the per-task clock reads altogether, and kernel:enableDiagnostics() turns profiling back on.
  • MicroProfiler zones (debug.profilebegin and profileend around every task, named by origin) add about 34 ns per task in this bench. They are on by default only in Studio. A live server opts in with Scheduler.setMicroProfilerZones(true).
  • The Serde fast engine against the wire-engine path on the same machine: fixed-schema encode went from 463 ns to 285 ns (38 percent faster), decode from 514 ns to 367 ns, and dynamic schemas got 22 to 27 percent faster, with identical bytes either way.
  • Bus:publish to a topic with no wildcard subscribers allocates nothing: the no-match case returns a shared frozen table.

Bench.runAll(container) requires every ModuleScript under the container whose name ends in .bench, sorted by name, and runs each registered case: 1,000 warm-up calls, then batches of 1,000 calls for 0.25 seconds. p50 and p99 are taken over the per-batch mean, and one frame passes between cases so they do not contend. Each result prints as one [Bench] line with ops per second, p50 and p99.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Bench = require(ReplicatedStorage.ChloeKernel.TestKit.Bench)
local Results = Bench.runAll(ReplicatedStorage.ChloeKernel.Benches)
for _, Result in Results do
print(Result.Name, Result.OpsPerSecond, Result.P50Nanos, Result.P99Nanos)
end

The runtime release model leaves out TestKit and Benches, so run the suite from the development project in Studio. Put game benchmarks in their own .bench modules and pass their folder to runAll; see Benchmark your own systems.

ChloeKernel v0.9.1. Access to the framework is private and granted individually by Chloe. Games built with it credit CryptedChloes in their description.