Skip to content

v0.8.0

Latest

Choose a tag to compare

@merlimat merlimat released this 31 Jul 12:09

Serde performance

This release is a systematic performance pass over the generated serialization/deserialization code. Measured against v0.7.3 on the same machine (interleaved A/B, JDK 26, Apple M-series):

Benchmark (ops/µs) v0.7.3 v0.8.0 Δ vs protobuf-java
Pulsar MessageMetadata deserialize 14.3 23.6 +66% 5.8×
Pulsar BaseCommand deserialize 33.2 46.7 +41% 3.8×
AddressBook deserialize 19.0 27.5 +45% 7.5×
Pulsar BaseCommand serialize 19.5 43.3 +122% 3.0×
Pulsar MessageMetadata serialize 12.0 14.5 +21% 5.2×

O(1) clear() with presence-guarded getters (#11)

clear() no longer walks every field: presence bits are tracked for all fields (including proto3 implicit presence) and getters return defaults for unset fields, so clearing is a handful of bitmask stores. Also fixes two latent bugs: clearX() on a message field left a stale child reachable, and copyFrom() overwrote target fields with unset implicit-presence source values (now follows protobuf merge semantics).

Serialization composes into a plain byte array (#12)

writeTo() builds the message in a byte[] — heap buffers in place, direct/composite targets via an instance-cached scratch and a single bulk writeBytes — eliminating per-field ByteBuf overhead, per-child ensureWritable/writerIndex round-trips for nested messages, and all sun.misc.Unsafe use in the write hot loop (which JDK 24+ taxes per call). writeTo(CompositeByteBuf) now works.

Presence-bit-driven traversal for union-like messages (#13)

Messages that declare many optional fields but set few (e.g. Pulsar's BaseCommand with ~50 sub-commands) now iterate the set bits of the presence masks in clear(), writeTo() and getSerializedSize() instead of testing every field.

Unchecked Netty reads for 64-bit varints (#14)

64-bit varints decode through a generated io.netty.buffer helper that skips Netty's per-byte bounds/accessibility checks and stores readerIndex once per varint. Truncated input still throws IndexOutOfBoundsException: a post-parse limit check restores the contract, with a bounded ≤9-byte in-buffer overrun on a truncated varint64 before the throw. Note: the helper class shares the io.netty.buffer package — fine on the classpath, a split package under the JPMS module path.

_isAscii via byteArrayViewVarHandle (#15)

String materialization's ASCII scan no longer uses sun.misc.Unsafe.getLong (an acquire-load per call since JDK 24, and on the removal track) — ~+4.5% materializing realistic topic-length strings.

Benchmark fix (#16)

SimpleBenchmark.lightProtoSerialize serialized only the nested Point; it now measures the whole Frame. The README benchmark table, speedup chart and raw JMH output are refreshed.

Deployment note

Netty's buffer checks can additionally be disabled process-wide with -Dio.netty.buffer.checkBounds=false -Dio.netty.buffer.checkAccessible=false: measured +8–17% deserialize and +1–4% serialize on top of the numbers above, with the usual global safety tradeoff.