Skip to content

[Java] Add sbe-jackson tree codec and benchmark harness - #1124

Closed
strangelydim wants to merge 9 commits into
masterfrom
polly/sbe-jackson-bench
Closed

strangelydim wants to merge 9 commits into
masterfrom
polly/sbe-jackson-bench

Conversation

@strangelydim

Copy link
Copy Markdown

Adds the sbe-jackson tree codec and a JMH harness for measuring stock-tree decoding, tree-fed encoding, and JSON serialization. The harness reuses the generated Car codecs, provides baseline and bounded heavy corpora, and checks adapter correctness during trial setup. Production tuning and borrowed-path benchmarks are deferred.

This draft includes prerequisite sbe-jackson commits because base commit 536825811 is not on a published base branch. The benchmark-only change is commit f7d733f82 (compare 536825811..f7d733f82); rebase or retarget after the prerequisite is reviewed.

Validation:

  • ./gradlew :sbe-benchmarks:build :sbe-jackson:check passed, including compilation with warnings as errors and Checkstyle; 177 collected Jackson test cases, zero failures/errors/skips.
  • java -jar sbe-benchmarks/build/libs/sbe-benchmarks.jar -l lists all eight new methods.
  • All 16 method/corpus combinations completed with -f 1 -wi 1 -i 1 -w 1s -r 1s -t 1 -prof gc, JDK 17.0.11, a 256 MiB heap, and fail-on-error enabled. These are smoke checks, not authoritative performance results.

See sbe-jackson/BENCHMARKS.md for commands, corpus details, allocation interpretation, and semantic differences between the baselines.

strangelydim and others added 9 commits September 16, 2026 11:17
Co-authored-by: omnigent <noreply@omnigent.ai>
Add the sbe-jackson Gradle module (settings include, version catalog
entries for Jackson 2.16.1 baseline and 2.21.6 latest, project block with
sbe-tool and jackson-databind api dependencies, generated test codecs,
jqwik in the test suite, and a testLatestJackson task that forces the
latest 2.21.x release). Add the policy enums, ErrorCode, Limits and
SbeJsonException described in DESIGN.md section 4.

Co-authored-by: omnigent <noreply@omnigent.ai>
… table

PlanCompiler walks the IR tokens of a message the way OtfMessageDecoder
does (block fields, then groups, then var-data, recursively) and records
one FieldPlan per field, composite member, group or var-data section with
scope-relative offsets, enum and bit set tables, group dimension and
var-data length layouts. MessagePlan carries the flat array, the root
child range and the sorted distinct sinceVersion thresholds that form
the effective-layout table. PlanCompilerTest compares the plan walk
against a spy TokenListener over OtfMessageDecoder for the Car, extension,
composite-elements, group-with-data and nested-group schemas.

Co-authored-by: omnigent <noreply@omnigent.ai>
…de and error model

SbeJson compiles every message of an Ir into a MessagePlan, builds the
JacksonCaches (constant nodes, enum name nodes, serialized field names)
and hands out thread-confined SbeJsonDecoder and SbeJsonEncoder
instances. SbeJsonDecoder performs the frame prologue (explicit
offset + length bounds, header via OtfHeaderDecoder, schema id and
template routing, NewerVersions.REJECT) and exposes lastHeader().
PlanMessageCodec.decodeCopy walks the plan into stock Jackson nodes:
scope-relative offsets, cursor = entryBase + actingBlockLength, present
fields validated inside the acting block, every read bounded by the frame
end, Limits on group entries, var-data bytes and depth, numInGroup
checked against the IR range, version-absent fields omitted. The
MessageCodec SPI, WalkContext (path and counters), HeaderLayout,
WireTypes and Utf8 helpers support both directions.

Tests: DecodeConformanceTest against JsonPrinter output, VersioningTest
(older messages with smaller root and group blocks, newer rejected,
header block length larger and smaller than the schema), LimitsTest
(hostile numInGroup, var-data budgets, depth pre-check, frame overflow
inside buffer capacity, null sentinel numInGroup, unknown template and
foreign schema id).

Co-authored-by: omnigent <noreply@omnigent.ai>
…tion, properties

PlanTreeEncoder walks the plan over a JsonNode tree: header from IR,
block fields at fixed offsets (zero-filled block so padding is
deterministic), groups and var-data in schema order, per-ObjectNode
unknown property detection via recognised count versus size(), strict
coercions from DESIGN.md section 7 and an optional sizing pass for
encodedLength. EncoderOracleTest proves byte equality with the generated
encoders for Car, extension Car, composite elements and group with data;
RoundTripTest covers bytes -> decodeCopy -> encode -> bytes and the
semantic fixed point under every policy combination plus property order
independence; EncodeValidationTest reaches every encode-side ErrorCode;
RoundTripPropertyTest uses jqwik for random values within schema ranges,
nested groups, UTF-8 var-data with surrogate pairs and lone surrogates.

Co-authored-by: omnigent <noreply@omnigent.ai>
Stringify generator system properties while retaining incremental inputs and outputs. Repair inherited IR and XML fixtures, constant assertions, composite error paths, and UTF-8 replacement expectations. Count base64 payload bytes before allocation without rejecting padding or whitespace at exact limits.

Verified ./gradlew :sbe-jackson:check :sbe-jackson:testLatestJackson :sbe-tool:check --rerun-tasks and incremental codec generation.

Co-authored-by: omnigent <noreply@omnigent.ai>
… strict text and retained destination

Fix loop 2 on the sbe-jackson module after cross-review of 59cc817.

Blocking:
- Optional scalars no longer accept their numeric null sentinel: the
  sentinel exception in signedValue / uint64Value / floatingValue now
  applies only to elements of optional numeric arrays. cupHolderCount: 255
  and optU64 at 2^64-1 are OUT_OF_RANGE for both encode and encodedLength;
  omission or JSON null still selects the sentinel.
- Optional float / double arrays with finite sentinels: elements equal to
  the sentinel at wire precision (float compares as float) are accepted, so
  omission -> encode -> decode -> re-encode is byte-identical and
  encodedLength accepts the decoded tree. New Arrays message in
  edge-cases-schema.xml carries float[3] (nullValue -1.1) and double[2]
  (nullValue -1) optional arrays plus a UTF-8 char[8].

Non-blocking:
- Var-data limit ordering is now proven: a TextNode subclass whose
  binaryValue() throws shows base64 payloads are rejected on their counted
  length, and a test-only charset (SpyCharset, registered through
  META-INF/services) records that boundedBytes hands the CharsetEncoder
  budget + 1 bytes of scratch, not the text's upper bound.
- PlanTreeEncoder.encode clears the destination in a finally so a retained
  encoder pins no caller buffer after success or failure.
- One text policy for fixed char arrays and var-data in every charset:
  malformed input (unpaired surrogates, unmappable characters) is rejected
  with TYPE_MISMATCH and never replaced. Utf8.encodedLength reports an
  unpaired surrogate as -1 and Utf8.encode refuses it. The property test
  that asserted U+FFFD replacement now asserts rejection. Documented in
  DESIGN.md sections 4, 7 and 9 and the SbeJsonEncoder javadoc.
- ProgrammaticIrs.edgeCases widens only the BigEnum BEGIN_ENUM..END_ENUM
  tokens (asserting 10 across messages and 5 in the type map) instead of
  every int64 token, and rewrites the captured type entry too.
- paddedBase64FitsExactPayloadLimits gains a limit + 1 negative case that
  must fail before binaryValue() runs.

Co-authored-by: omnigent <noreply@omnigent.ai>
Add shared Car corpus state, stock-tree decode baselines, tree-fed encode benchmarks, and JSON serialization baselines. Document execution and allocation caveats without tuning production code.

Co-authored-by: omnigent <noreply@omnigent.ai>
@strangelydim

Copy link
Copy Markdown
Author

Opened by mistake by an automated worker; this work is intentionally local-only for now.

@strangelydim
strangelydim deleted the polly/sbe-jackson-bench branch September 17, 2026 16:06
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