You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
We replaced a vulcan Codec.toBinary on a hot produce path with two alternatives, both producing byte-identical Avro binary (verified by property tests against the vulcan bytes):
Hand-written: fields written directly to an org.apache.avro.io.BinaryEncoder in schema order (writeIndex / writeString / writeLong …). Union payloads with ~18 branches still go through cached GenericDatumWriters.
eo / kindlings:AvroEncoder.derived (via AvroCodec.derived) builds the Avro generic record, and a plain Avro GenericDatumWriter serializes it under the registered writer schema. Custom leaf encoders handle micros instants and map key order. A nested sub-record is spliced in with AvroPrism.graftBytes.
Both replace the same vulcan path. The record is a ~15-field top level with a nested ~55-field all-Option metrics record, an 18-branch union, and a ~20-field nested record that is either spliced (graft) or null.
The eo route is a big win over vulcan (3.8× / 2.3×), but it's about 1.4–1.7× slower than the hand-written writer and allocates 9–18% more. Caveat: the hand-written and eo numbers come from separate runs on the same machine; allocation should be reliable, timings indicative.
Hypothesis
The gap is the intermediate generic-record tree. AvroEncoder.derived materializes a GenericData.Record (plus boxed leaves / java.util collections) for every nested record, which GenericDatumWriter then walks with per-field schema dispatch. The hand-written path streams straight to the encoder.
If not, is there a recommended pattern for writing a derived record directly to bytes (e.g. an AvroCodec method returning Array[Byte] that skips GenericData.Record)?
Is the extra allocation expected from the derived encoders (boxing Option / numeric leaves), or is it avoidable?
Context
We replaced a vulcan
Codec.toBinaryon a hot produce path with two alternatives, both producing byte-identical Avro binary (verified by property tests against the vulcan bytes):org.apache.avro.io.BinaryEncoderin schema order (writeIndex/writeString/writeLong…). Union payloads with ~18 branches still go through cachedGenericDatumWriters.AvroEncoder.derived(viaAvroCodec.derived) builds the Avro generic record, and a plain AvroGenericDatumWriterserializes it under the registered writer schema. Custom leaf encoders handle micros instants and map key order. A nested sub-record is spliced in withAvroPrism.graftBytes.Both replace the same vulcan path. The record is a ~15-field top level with a nested ~55-field all-
Optionmetrics record, an 18-branch union, and a ~20-field nested record that is either spliced (graft) or null.Numbers (JMH,
-prof gc, 1 fork, 5 warmup + 8 measurement iterations, µs/op and B/op)The eo route is a big win over vulcan (3.8× / 2.3×), but it's about 1.4–1.7× slower than the hand-written writer and allocates 9–18% more. Caveat: the hand-written and eo numbers come from separate runs on the same machine; allocation should be reliable, timings indicative.
Hypothesis
The gap is the intermediate generic-record tree.
AvroEncoder.derivedmaterializes aGenericData.Record(plus boxed leaves /java.utilcollections) for every nested record, whichGenericDatumWriterthen walks with per-field schema dispatch. The hand-written path streams straight to the encoder.Questions / possible directions
A => BinaryEncoder => Unit) that writes fields in schema order without building the generic tree, keepingAvroCodec[A]as the typeclass? That's roughly the encode-side counterpart of what No efficient whole-record construction primitive (build-from-scratch beats AvroRecordPrism) #95 addressed for construction.AvroCodecmethod returningArray[Byte]that skipsGenericData.Record)?derivedencoders (boxingOption/ numeric leaves), or is it avoidable?Versions: cats-eo-avro 0.14.0, kindlings-avro-derivation 0.3.2, hearth 0.4.2, avro 1.12.1, Scala 3.9.0, JDK 25 (Corretto).
Happy to share a reduced JMH reproducer if useful.