Skip to content

bench: pony_bench-based benchmark suite for the 0.2.0 surface - #10

Merged
redvers merged 4 commits into
mainfrom
bench/initial-suite
Jun 18, 2026
Merged

bench: pony_bench-based benchmark suite for the 0.2.0 surface#10
redvers merged 4 commits into
mainfrom
bench/initial-suite

Conversation

@redvers

@redvers redvers commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Establishes a performance baseline before encoding work begins in 0.3.0 and so that implementation tweaks have attributable numbers.

Layout mirrors our existing build-tool split:

unicode_bench/ package: harness, corpora, 15 topic primitives
_bench.pony BenchSizes / _Overhead / _Bench / _ValBench
/ _CtorBench / _BenchPair / _BenchU32
corpora.pony 7 generators: ascii / latin_precomposed /
latin_decomposed / cjk / mixed / emoji /
combining_marks
bench
*.pony 15 topic registrations (Bytes, Text,
Codepoints, Graphemes, Words, Sentences,
Lines, Normalize, Case, Compares, Search,
Split, Trim, Replace, Scripts)

unicode_bench_main/ binary entry point — actor Main is
main.pony BenchmarkList; registers every topic against
PonyBench in create.

The harness adapts the working pieces of the sibling string_benchmark project — size buckets (16B → 160M), the per-bucket cfg(size) that scales samples and iteration caps so large sizes don't generate gigabytes of garbage per sample, the config-aware overhead bench. New wrappers: _BenchPair for binary ops (Compares.equal_*, Search.contains), _BenchU32 for single-cp property lookups.

Each bench_* topic exposes a public primitive with a register(bench: PonyBench, sizes: Array[USize] val) method. Sizes are passed in from main so smoke (single small bucket) and full-suite runs share the same registration code.

Makefile targets (NOT part of ci; benchmark runtimes are far too long for every PR):

make bench-build release-mode compile
make bench full suite
make bench-smoke --smoke single small bucket
make bench-csv -csv into build/bench-.csv

Validated end-to-end on the smoke bucket. Spot-check from the run: Bytes.is_valid_utf8/ascii/1K ≈ 4.7µs vs cjk/1K ≈ 5.5µs — the 15% delta from multi-byte UTF-8 work confirms the harness measures real cost rather than noise.

Pony / LLVM gotchas worked around during bring-up: DoNotOptimise generic specialization fails on (USize, USize) tuples (LLVM crash) and on unions containing String iso^ ephemeral types. Tuple cases extract (let lo, let hi) then pass the sum; iso cases use (String iso | InvalidUtf8) without the ephemeral marker.

redvers added 4 commits June 18, 2026 11:45
Establishes a performance baseline before encoding work begins in 0.3.0
and so that implementation tweaks have attributable numbers.

Layout mirrors our existing build-tool split:

  unicode_bench/         package: harness, corpora, 15 topic primitives
    _bench.pony          BenchSizes / _Overhead / _Bench / _ValBench
                         / _CtorBench / _BenchPair / _BenchU32
    _corpora.pony        7 generators: ascii / latin_precomposed /
                         latin_decomposed / cjk / mixed / emoji /
                         combining_marks
    bench_*.pony         15 topic registrations (Bytes, Text,
                         Codepoints, Graphemes, Words, Sentences,
                         Lines, Normalize, Case, Compares, Search,
                         Split, Trim, Replace, Scripts)

  unicode_bench_main/    binary entry point — actor Main is
    main.pony            BenchmarkList; registers every topic against
                         PonyBench in create.

The harness adapts the working pieces of the sibling
`string_benchmark` project — size buckets (16B → 160M), the per-bucket
`cfg(size)` that scales samples and iteration caps so large sizes
don't generate gigabytes of garbage per sample, the config-aware
overhead bench. New wrappers: `_BenchPair` for binary ops
(`Compares.equal_*`, `Search.contains`), `_BenchU32` for single-cp
property lookups.

Each `bench_*` topic exposes a public primitive with a
`register(bench: PonyBench, sizes: Array[USize] val)` method. Sizes
are passed in from main so smoke (single small bucket) and full-suite
runs share the same registration code.

Makefile targets (NOT part of `ci`; benchmark runtimes are far too
long for every PR):

  make bench-build   release-mode compile
  make bench         full suite
  make bench-smoke   --smoke single small bucket
  make bench-csv     -csv into build/bench-<sha>.csv

Validated end-to-end on the smoke bucket. Spot-check from the run:
Bytes.is_valid_utf8/ascii/1K ≈ 4.7µs vs cjk/1K ≈ 5.5µs — the 15%
delta from multi-byte UTF-8 work confirms the harness measures real
cost rather than noise.

Pony / LLVM gotchas worked around during bring-up: `DoNotOptimise`
generic specialization fails on `(USize, USize)` tuples (LLVM crash)
and on unions containing `String iso^` ephemeral types. Tuple cases
extract `(let lo, let hi)` then pass the sum; iso cases use
`(String iso | InvalidUtf8)` without the ephemeral marker.
Two operations cost minutes per single iteration at the 160M bucket,
which makes the suite intractable for routine before/after diffing:

- `Text.from_string[indexed]` builds an O(n) grapheme bitmap on top of
  validation. From the partial CSV: ~750 ns/byte at the 15M bucket,
  projecting to ~2 min per iter at 160M; with 5 samples + the paired
  overhead bench, ~30-45 min per Text-indexed bench triple.
- `Normalize.nfc/combining` runs the canonical reorder over a
  6-codepoint combining run per base letter, then composes. Same
  shape — likely 10+ min per bench at 160M.

`BenchSizes.heavy_op_cap() = 16_000_000` is the new ceiling for these
registrations. Other ops still run at every bucket; tighten the cap
later if we find more offenders, or loosen it once an implementation
gets fast enough to make 160M tractable.

Also keeps the `tee`-based `bench-csv` target from the merge so CSV
output streams to the terminal as well as the file.
`BenchSizes.label` previously rounded down to a K or M suffix, which
lost precision: the 1_600-byte bucket showed up as "1K" (not 1 KiB);
the 160_000_000-byte bucket as "152M" (not 152 MiB). Bench output now
reads `Bytes.is_valid_utf8/ascii/1600B` and
`Text.from_string/emoji/160000000B` — uglier but unambiguous, and
the CSV column sorts numerically by suffix-stripped digits.
@redvers
redvers merged commit 10712bf into main Jun 18, 2026
6 checks passed
@redvers
redvers deleted the bench/initial-suite branch June 18, 2026 16:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant