From bb1e1d6852272cb8a2a32285c10ddee695d680ec Mon Sep 17 00:00:00 2001 From: Mitsunori Komatsu Date: Sun, 27 Sep 2026 13:04:42 +0900 Subject: [PATCH 1/4] Record benchmark results since the last entry The two measured wins from late in the Jackson 3 rewrite (write +8.8% from one wrap helper, read allocation 2056 to 1912 B/op from not boxing integers), and a back-to-back comparison of the rewrite against main after the map-key and UUID changes. --- jmh/results/2026-09-22-write-wrap-helper.md | 20 +++++++++++ .../2026-09-23-integer-without-boxing.md | 21 +++++++++++ jmh/results/2026-09-27-pr9-vs-main.md | 35 +++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 jmh/results/2026-09-22-write-wrap-helper.md create mode 100644 jmh/results/2026-09-23-integer-without-boxing.md create mode 100644 jmh/results/2026-09-27-pr9-vs-main.md diff --git a/jmh/results/2026-09-22-write-wrap-helper.md b/jmh/results/2026-09-22-write-wrap-helper.md new file mode 100644 index 0000000..4962c31 --- /dev/null +++ b/jmh/results/2026-09-22-write-wrap-helper.md @@ -0,0 +1,20 @@ +# One wrap helper for keys and container headers + +Measured on the same machine as the earlier files, the commit and its parent run back to +back. The exact JMH options of this run were not recorded. + +## Change + +`f8695a4`: `writeName`, `openContainer` and `closeContainer` each wrapped their writer call in +their own try/catch that turned an `IOException` into a Jackson exception. The try/catch now +lives in one `pack()` helper, which makes those three hot methods small enough to inline +better. + +## Result + +| Benchmark | Before | After | Change | +|---|---|---|---| +| writePojoJson | 1099800 ± 14428 | 1088509 ± 13948 | control | +| writePojoMsgpack | 1170317 ± 9411 | **1273380 ± 12473** | **+8.8%** | + +The error bars do not overlap and the control is flat. diff --git a/jmh/results/2026-09-23-integer-without-boxing.md b/jmh/results/2026-09-23-integer-without-boxing.md new file mode 100644 index 0000000..588840a --- /dev/null +++ b/jmh/results/2026-09-23-integer-without-boxing.md @@ -0,0 +1,21 @@ +# Reading an integer without boxing it + +Same machine as the earlier files, the commit and its parent run back to back, with +`-prof gc` for the allocation figures. The exact JMH options of this run were not recorded. + +## Change + +`1037af9`: the parser boxed every integer it read into an `Integer` or `Long`. It now keeps the +value in a primitive field. + +## Result + +| Benchmark | Before | After | +|---|---|---| +| readPojoMsgpack allocation | 2056 B/op | **1912 B/op** | +| readPojoJson allocation (control) | 2096 B/op | 2096 B/op | + +144 bytes per operation are gone. MessagePack read now allocates about 9% less than Jackson's JSON reader on the same POJO. + +Throughput did not resolve on this machine (694079 ± 11508 before, 680121 ± 7908 after, with +the JSON control moving from 674k to 677k), so the allocation figure is the result here. diff --git a/jmh/results/2026-09-27-pr9-vs-main.md b/jmh/results/2026-09-27-pr9-vs-main.md new file mode 100644 index 0000000..862a372 --- /dev/null +++ b/jmh/results/2026-09-27-pr9-vs-main.md @@ -0,0 +1,35 @@ +# PR #9 against main after the map-key and UUID changes + +A check that the changes merged after the Jackson 3 rewrite (#9) did not cost performance: +#10 (string tokens read as binary), #16 (MessagePackKeySerializer removed, key guard added) and +#17 (UUID written as a string). + +Same machine as the earlier files. Both commits were built and run back to back with +`java -jar jmh/build/libs/jmh-jmh.jar "MsgpackWriteBenchmark|MsgpackReadBenchmark|WriteUTF8StringBenchmark|WriteStringBenchmark" -f 5 -wi 5 -w 1 -i 10 -r 2 -bm thrpt -tu s` +(50 samples per benchmark), PR #9 (`bb4158f`) first, then main (`cff8bfc`). + +The numbers are higher than the ones #9 recorded in `2026-09-21-single-pass-short-strings.md` +for the same code (writePojoMsgpack 1242839 here, 1092242 there), so only runs made back to +back are compared. + +## Result + +| Benchmark | PR #9 | main | Change | +|---|---|---|---| +| readPojoJson | 686406 ± 11655 | 682182 ± 10299 | control | +| readPojoMsgpack | 686441 ± 7242 | 689769 ± 5651 | +0.5%, within error | +| writePojoJson | 1091002 ± 5201 | 1072677 ± 11429 | control, -1.7% | +| writePojoMsgpack | 1242839 ± 9565 | 1237674 ± 8973 | -0.4%, within error | +| WriteStringBenchmark.shortAscii | 128397 ± 2511 | 128497 ± 2274 | within error | +| WriteStringBenchmark.shortNonAscii | 85536 ± 3778 | 89744 ± 2307 | within error | +| WriteStringBenchmark.shortNonAsciiShifted | 48127 ± 1499 | 46294 ± 1721 | within error | +| WriteStringBenchmark.longAscii | 81484 ± 1344 | 80275 ± 265 | within error | +| WriteStringBenchmark.longNonAscii | 55262 ± 518 | 55197 ± 187 | within error | +| WriteUTF8StringBenchmark.writeUTF8StringAscii | 118398 ± 1288 | 117607 ± 667 | within error | +| WriteUTF8StringBenchmark.writeUTF8StringNonAscii | 115120 ± 1381 | 112199 ± 1320 | -2.5% | + +POJO read and write are on par with #9. The only MessagePack row whose error bars do not +overlap, writeUTF8StringNonAscii at -2.5%, moved while the JSON write control, whose code is +the same in both builds, dropped 1.7%, so the machine was slower during the second run. That +row is inconclusive rather than a confirmed regression; running the string benchmarks again +with main first would tell drift from a real cost. From 8b4a56f16f9d9cb51bdd65eeb3140e0b05556707 Mon Sep 17 00:00:00 2001 From: Mitsunori Komatsu Date: Sun, 27 Sep 2026 13:04:47 +0900 Subject: [PATCH 2/4] Add AGENTS.md Commands, and the rules this repository works by: round-trip safety first, no Jackson 2 compatibility goal, failure output is the caller's to discard, failing-first tests, long back-to-back benchmarks with a reused mapper, results in jmh/results, and Copilot reviews. --- AGENTS.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d5306e5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,48 @@ +# AGENTS.md + +Guidance for coding agents working in this repository. The design itself (write and read +paths, buffer ownership, map keys, the parser's accessor contract) is in `docs/DESIGN.md`; +read it before changing the generator, parser, writer or reader. + +## Project + +A Jackson 3 dataformat for MessagePack, `org.komamitsu:jackson-dataformat-msgpack`, with its +own MessagePack encoder and decoder. msgpack-core is a test dependency only, used as the +reference implementation in tests. + +## Commands + +```bash +./gradlew clean build # Compile, checkstyle, all tests (Java 17 toolchain) +./gradlew build -PtestJavaVersion=21 # Run the tests on another JDK, as CI does for 17, 21 and 24 +./gradlew soakTest -Psoak.seconds=120 # Memory-growth soak tests, excluded from the normal build +./gradlew :jmh:jmhJar # Build the benchmark jar +``` + +Tests run with `-Dfile.encoding=windows-1252`, so a conversion that forgets to name a charset +fails in the tests. + +## Rules + +- **Round-trip safety comes first.** Whatever this library writes, it must be able to read + back. A type that cannot round-trip is refused on write rather than written in a form that + fails on read (see `UnreadableKeyGuard` and DESIGN.md 2.6). +- **Compatibility with the Jackson 2 module (`msgpack-jackson` in msgpack-java) is not a + goal.** Do not keep or add behaviour only because that module had it. +- **Output after a serialization failure is the caller's to discard.** Do not add rollback or + cleanup machinery for callers that catch the exception and keep using the generator. +- **Bug fixes come with a test that fails without the fix.** Assert exact values and bytes, + not only types or non-null. +- **Benchmark changes to a hot path before pushing.** Build the change and `main` and run them + back to back: + `java -jar jmh/build/libs/jmh-jmh.jar -f 5 -wi 5 -w 1 -i 10 -r 2`, adding + `--add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED` + through `-jvmArgs`. Measure with a reused mapper; a benchmark that creates a mapper per + operation measures nothing users see. Numbers from different days are not comparable on + this machine, and a short run is not enough to call a change free. +- **Record meaningful results in `jmh/results/`**, one file per change, with the command, the + compared commits and the JSON control. +- **Reviews** go through GitHub Copilot, requested with + `gh api -X POST repos/komamitsu/jackson-dataformat-msgpack/pulls//requested_reviewers -f "reviewers[]=copilot-pull-request-reviewer[bot]"`. + Check each finding against the code, with a test where it makes a claim about behaviour, + before fixing or declining it. From 6ca928e2de0a2e181277b7fa88ebf89239a874bc Mon Sep 17 00:00:00 2001 From: Mitsunori Komatsu Date: Sun, 27 Sep 2026 13:48:34 +0900 Subject: [PATCH 3/4] Name the compared commits in the older results --- jmh/results/2026-09-22-write-wrap-helper.md | 9 +++++++-- jmh/results/2026-09-23-integer-without-boxing.md | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/jmh/results/2026-09-22-write-wrap-helper.md b/jmh/results/2026-09-22-write-wrap-helper.md index 4962c31..d5922c8 100644 --- a/jmh/results/2026-09-22-write-wrap-helper.md +++ b/jmh/results/2026-09-22-write-wrap-helper.md @@ -1,7 +1,12 @@ # One wrap helper for keys and container headers -Measured on the same machine as the earlier files, the commit and its parent run back to -back. The exact JMH options of this run were not recorded. +Measured on the same machine as the earlier files, run back to back: + +- Before: `c2d1248` (Write scalar values through one verify-and-wrap helper) +- After: `1af13b5`, which adds `f8695a4` below and `1af13b5` (repeated parser errors built in + one place, a read-side change that no write benchmark runs) + +The JMH command of this run was not recorded, so it is not given here. ## Change diff --git a/jmh/results/2026-09-23-integer-without-boxing.md b/jmh/results/2026-09-23-integer-without-boxing.md index 588840a..47dfb5f 100644 --- a/jmh/results/2026-09-23-integer-without-boxing.md +++ b/jmh/results/2026-09-23-integer-without-boxing.md @@ -1,7 +1,12 @@ # Reading an integer without boxing it -Same machine as the earlier files, the commit and its parent run back to back, with -`-prof gc` for the allocation figures. The exact JMH options of this run were not recorded. +Same machine as the earlier files, with `-prof gc` for the allocation figures, run back to +back: + +- Before: `d2d774e` (Name the context methods for what they do) +- After: `1037af9` below + +The rest of the JMH command of this run was not recorded, so it is not given here. ## Change From b8f365e32633da5c2c4076d6102c64fed68154af Mon Sep 17 00:00:00 2001 From: Mitsunori Komatsu Date: Sun, 27 Sep 2026 16:04:16 +0900 Subject: [PATCH 4/4] Describe the after state of the write-helper run once --- jmh/results/2026-09-22-write-wrap-helper.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jmh/results/2026-09-22-write-wrap-helper.md b/jmh/results/2026-09-22-write-wrap-helper.md index d5922c8..06bf0a5 100644 --- a/jmh/results/2026-09-22-write-wrap-helper.md +++ b/jmh/results/2026-09-22-write-wrap-helper.md @@ -3,8 +3,9 @@ Measured on the same machine as the earlier files, run back to back: - Before: `c2d1248` (Write scalar values through one verify-and-wrap helper) -- After: `1af13b5`, which adds `f8695a4` below and `1af13b5` (repeated parser errors built in - one place, a read-side change that no write benchmark runs) +- After: `1af13b5`. Two commits on top of the before state: `f8695a4` (the change below) and + a read-side change that builds repeated parser errors in one place, which no write benchmark + runs The JMH command of this run was not recorded, so it is not given here.