jonnify·bcs /bcs/ideas/branding-beats-its-own-integer/the-five-runtimes

B1.6 · dive — the five runtimes

One contract, five tongues, five clocks.

The encode under test is the same contract in five implementations, each cited at its file and line, each measured against the idiomatic decimal rendering of its own runtime. The compiled runtimes agree emphatically, Go records a split across API regimes, the BEAM records a tie, and V8 records the one loss — in exactly the place the contract prescribes the wasm crossing.

Source: content/bcs1.a1.md · the five implementations + measured; outputs committed under bench/branding-vs-decimal/.

Pick a runtime, read its clock. The readout quotes the committed numbers and computes the ratio live — a division over the fixed table, nothing re-measured.
C branded_id.h:37 7.21 ns/op Rust lib.rs:71 5.14 ns/op Go brandedid.go:40 40.02 ns/op Elixir branded_id.ex:67 132.5 ns/op Node branded_id.ts:82 381 ns/op
readout · Select a runtime. Static reading: branded against decimal, in ns/op — C 7.21 / 20.49, Rust 5.14 / 21.77 itoa · 31.61 to_string, Go 40.02 / 48.29 FormatUint · 25.87 AppendUint, Elixir 132.5 / 133.6, Node 381 / 45.6.
§1

The five implementations

content/bcs1.a1.md · cited at the surface
content/bcs1.a1.md · the encode surfaces, each at its file and linefrozen
contract/include/branded_id.h:37   branded_status branded_encode(const char ns[3], uint64_t snowflake, char out[…])
contract/branded-id-rs/src/lib.rs:71   pub fn encode(ns: &[u8; NS_LEN], snowflake: u64) -> Result<[u8; LEN], Error>
runtimes/go/brandedid/brandedid.go:40   func Encode(ns string, snow uint64) (string, error)
runtimes/elixir/lib/echo_data/branded_id.ex:67   def encode(<<_::binary-size(3)>> = ns, snow)
runtimes/node/branded_id.ts:82   export const encode = <N extends string>(ns: N, snow: bigint): Result<BrandedId<N>>

The harnesses live beside the outputs: c_bench.c, the crate’s examples/branding_bench.rs, branding_bench_test.go in the Go module, bench_branding.exs, and branding_bench.ts. The decimal opponents are the idiomatic forms per runtime, with a no-allocation variant added where the idiom allocates.

§2

Measured

bench/branding-vs-decimal
content/echo_data/bench/branding-vs-decimal/{c,rust,go,elixir,node}_bench.outfrozen
runtime              branded encode      decimal encode                        committed output
C (cc 13.3, -O2)     7.21 ns/op          20.49 ns/op  (itoa)                   c_bench.out
Rust 1.75 (-O)       5.14 ns/op          21.77 itoa · 31.61 to_string          rust_bench.out
Go 1.22.2            40.02 ns/op         48.29 FormatUint · 25.87 AppendUint   go_bench.out
Elixir (native)      132.5 ns/op         133.6 ns/op                           elixir_bench.out
Node 22 (pure TS)    381 ns/op           45.6 ns/op  (BigInt toString)         node_bench.out

Method, as recorded: the low 20 bits varied to defeat constant folding; a byte of each output accumulated into a sink to defeat dead-code elimination; best of three runs, five on Node. One host, the recorded toolchains — ratios travel better than absolute numbers.

§3

The verdicts

content/bcs1.a1.md · measured, read row by row

C and Rust — the derivation cashes. The brand encodes 2.8× faster than a tight hand itoa in C (7.21 against 20.49) and 4.2× faster in Rust (5.14 against the fair fixed-buffer 21.77; 6.1× against the idiomatic allocating to_string at 31.61). Eleven divisions into fixed positions beat nineteen into a width the code must then account for — anywhere a hot path renders ids in compiled code, the branded form is the cheap one, in bytes and in cycles at once.

Go — a split decided by allocation, not by alphabet. MustEncode returns a string and beats its allocation-fair twin FormatUint (40.02 against 48.29), while the no-allocation AppendUint floor wins at 25.87 — the comparison crosses API regimes, not encoding costs. An append-style branded encode writing into a caller buffer is recorded as a follow-up rather than claimed.

Elixir — a tie, and an informative one. 132.5 against 133.6 nanoseconds — the cost of crossing into the native codec equals the BEAM’s own integer-to-string machinery almost exactly. The brand is free where it matters most in this series’ reference runtime, and the conformance suite already guarantees the pure path agrees on every byte.

Node — the recorded loss, in the prescribed place. Userland BigInt base62 pays interpreter-level costs per divmod; V8’s decimal conversion is engine C++ — 381 against 45.6, an 8.4× deficit. This is precisely the boundary where the contract prescribes the wasm crossing: the wasm codec already serves decode and hash32 through the loader, and an encode export is the carried follow-up. Until it lands, the next dive’s arithmetic keeps the loss in proportion.

§4

The Go split, read closely

go_bench.out · three API regimes
Three numbers, two regimes. Select an API; the readout names its regime — the move this lesson teaches is reading a benchmark’s API boundary before its numbers.
readout · Select an API. Static reading: MustEncode 40.02 and FormatUint 48.29 both return a string; AppendUint 25.87 writes into a caller buffer — the split is allocation, not alphabet.

The branded row wins its allocation-fair comparison and loses to a floor it was never offered: no append-style branded encode exists yet, so the no-allocation regime has only a decimal entrant. The derivation places an append-style branded encode near the C number — recorded as a follow-up rather than claimed, which is the manuscript’s evidence ethic applied to its own prediction.

§5

References

sources · related

Sources

Related