Skip to content

benchmark: compile time & serialization vs. popular Scala libraries - #47

Merged
halotukozak merged 1 commit into
mainfrom
benchmarks
Sep 2, 2026
Merged

benchmark: compile time & serialization vs. popular Scala libraries#47
halotukozak merged 1 commit into
mainfrom
benchmarks

Conversation

@halotukozak

@halotukozak halotukozak commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

What

A standalone benchmark suite (benchmark/, a separate scala-cli build) comparing mcodec's compile time and serialization throughput against the field: circe, jsoniter-scala, uPickle, zio-json, borer, play-json, and AVSystem GenCodec (the design mcodec is modelled on).

Full write-up + charts: docs/benchmarks.md.

Layout

path what
benchmark/src/models/ 4 shared, library-neutral models: flat primitives, deep nesting, a recursive GeoJSON-style ADT, a 500-event collection batch
benchmark/src/codecs/ per-library codec instances behind one JsonCodec facade
benchmark/src/bench/ JMH state classes (library is a @Param)
benchmark/gencodec/ nested Scala 2.13 build — GenCodec has no Scala 3 release; same models, feeds both the runtime tables and the compile sweep
benchmark/compile/ generator + clean-compile sweep over N derived codecs, with scalac phase breakdown
benchmark/scripts/ aggregate.py / plot.py → CSVs, charts, tables injected into the report; run_all.sh drives it all

No CI — run it locally with benchmark/scripts/run_all.sh.

Headline numbers (laptop run — indicative, not citable)

  • Runtime: mcodec matches GenCodec on reads, ~40 % behind on writes (except the recursive ADT, where it edges ahead). Mid-pack in the Scala 3 field; jsoniter-scala is in its own league.
  • Compile time: the transparent inline derivation is the steepest-scaling of the group — ~10 s for 50 + 12 codecs vs. ~4–6 s for the Scala 3 macro libraries and ~4 s for GenCodec's Scala 2 macro — and emits ~3.5× more bytecode than circe/jsoniter. Cost concentrated in typer + inlining.

Notes for review

  • scala-cli has no directive to exclude a subtree from scala-cli ., so ci.yml, docs.yaml, mima.yml, publish.yml now pass --exclude benchmark (and benchmark/gencodec builds separately). ci.yml / docs.yaml / mima.yml are synced from shared/ — these edits need upstreaming to the templates or they revert on the next repo-sync.
  • mcodec is consumed as a published dependency: scala-cli --power publish local . --exclude benchmark --project-version 0.0.0-BENCH --doc=false.
  • benchmark/gencodec/Bench.scala starts with // format: off — the repo scalafmt applies Scala-3-only rewrites that 2.13 can't parse.
  • Committed CSVs/charts are from an M4 Pro laptop; regenerate on a quiet box for authoritative numbers.

🤖 Generated with Claude Code

Copilot AI lite review requested due to automatic review settings September 2, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Adds a standalone benchmark module (benchmark/, a separate scala-cli build) that
measures the two axes that matter for a derivation codec against circe,
jsoniter-scala, uPickle, zio-json, borer, play-json and AVSystem GenCodec (the
design mcodec is modelled on):

- serialization/deserialization throughput (JMH) over 4 models: flat primitives,
  deep nesting, a recursive GeoJSON-style ADT, and a 500-element collection batch
- clean-compile time swept over N derived codecs, with a scalac phase breakdown
  and emitted-bytecode size

GenCodec has no Scala 3 release, so benchmark/gencodec/ is a nested Scala 2.13
build with the same models; its numbers feed both the runtime tables and the
compile sweep (the latter flagged as a cross-ecosystem, not like-for-like, point).

Pipeline: scripts/{aggregate,plot}.py turn the raw JMH/compile output into CSVs,
charts and the tables in docs/benchmarks.md; scripts/run_all.sh drives the lot.
There is no CI for the benchmark module — run it locally. scala-cli has no
directive to exclude the benchmark tree from `scala-cli .`, so the main
CI/publish/mima/docs workflows and the README build commands pass
--exclude benchmark.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCfiQMwXigsoCeMcR3jXP6
@halotukozak
halotukozak merged commit e7d6bdc into main Sep 2, 2026
20 of 22 checks passed
@halotukozak
halotukozak deleted the benchmarks branch September 2, 2026 11:34
halotukozak added a commit that referenced this pull request Sep 2, 2026
The MiMa baseline is now a worktree of a commit that contains benchmark/
(everything since #47), so `scala-cli package "$RUNNER_TEMP/old-src"` picks up
benchmark/gencodec (Scala 2.13) and fails to resolve its deps. Completes the
--exclude benchmark rollout started in #47.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCfiQMwXigsoCeMcR3jXP6
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.

2 participants