Repository navigation
Blackbird: use generated accessors for primitive double properties - #375
Conversation
| // Renamed (as for an unwrapped property, after its serializer was assigned): | ||
| // the fallback must write the new name, and a writer that already uses it | ||
| // (like one with a non-default serializer) must keep doing so | ||
| fallbackWriter = _renamed(base.fallbackWriter, name); |
There was a problem hiding this comment.
Drive-by fix: inside @JsonUnwrapped(prefix = ...), a renamed writer used to forget broken and keep a fallback with the old name, so the STRING shape and custom serializers were ignored for every type. Doubles would have picked this up, so I fixed it here. TestUnwrappedWriters covers it.
| return false; | ||
| } | ||
| // Nor one that writes null whatever the value (as used to hide one) | ||
| if (ser instanceof NullSerializer) { |
There was a problem hiding this comment.
Drive-by fix: NullSerializer is @JacksonStdImpl, so it counted as a default serializer and int/long/boolean/String getters wrote their value instead of null. Same exclusion as ToStringSerializer (#117).
There was a problem hiding this comment.
Could you file an issue for https://github.com/FasterXML/jackson-databind/issues for this? (to remove that annotation)
| value = _acc.applyAsLong(bean); | ||
| } catch (Throwable t) { | ||
| _handleProblem(bean, gen, prov, t, false); | ||
| _handleProblem(bean, gen, prov, t, true); |
There was a problem hiding this comment.
Drive-by fix: this was false, so after an access error an as-array bean fell back to writing a field instead of an element. The other writers already pass true.
|
@parker89 made a change based on Claude review to fix support of Custom filters for |
|
Thanks for the review! |
Follow-up to #364. Blackbird's specialized accessors came over from Afterburner, which never had one for
double, so a primitivedoublesetter or getter still went through reflection and boxed on every call. For a bean that's mostly doubles, Blackbird did about as well as plain databind.This adds
SettableDoublePropertyandDoublePropertyWriter, ported from the current long versions and following the same rules from #364, #366 and #367: setup checks, exact-type and receiver checks, null handling and failure reporting that matchMethodProperty, and no retries.The main win is allocation: 24 bytes less for each double read or written. Throughput improves a lot where number parsing and formatting are cheap or skipped (a
TokenBuffer, as withconvertValue, or a binary format), and a little with JSON.Matching stock:
VALUE_NUMBER_FLOAT, read withgetDoubleValue(), which is exactly whatDoubleDeserializerdoes for that token. Integers, Strings (like "NaN"), nulls and arrays still go through the stock deserializer, so coercion config andFAIL_ON_NULL_FOR_PRIMITIVESbehave as before.set()andsetAndReturn()only use the generated setter for aDouble.Double.equalsdoes (-0.0isn't0.0, NaN matches NaN), gives a customvalueFilterthe boxed value, and leaves a getter with a type serializer toBeanPropertyWriter, sinceDoubleSerializerwrites a type id for NaN and infinities. The STRING shape and custom serializers use the fallback writer, as for the other types.Also fixed, for all property types:
@JsonUnwrapped(prefix = ...), the STRING shape and custom serializers were ignored ({"p_l":5}instead of{"p_l":"5"}), because a renamed writer droppedbrokenand its fallback kept the old name.NullSerializer, set with@JsonSerialize(using = ...)or registered for a type, was treated as a default serializer, so int, long, boolean and String getters wrote their value instead of null. It's now excluded, asToStringSerializeralready is (Use ofToStringSerializervia@JsonSerializeonint/longproperty does not work #117).LongPropertyWriter.serializeAsElementfell back as a field, not an element, after an access error.Not changed, as other types already behave this way: the int and long writers ignore
CUSTOMvalue filters, regular bean accessors don't checkCAN_OVERRIDE_ACCESS_MODIFIERS, and a getter that fails with an access error is retried through reflection.Tests compare against vanilla Jackson across inputs, mapper and inclusion settings, shapes, special values (
-0.0, NaN, infinities, min/max), regular beans, fluent setters, creators, builders, as-array and unwrapped beans. All Blackbird tests pass on Java 8, 11, 17 and 25.The new benchmarks use 1,000 beans per operation, each with six doubles and an int. JDK 25 on an 8-core Linux box, 4 forks each, alternating base (
2.x) and head:doubleBeanArrayBufferDeserdoubleBeanArrayBufferSerdoubleBeanArrayDeserdoubleBeanArraySerdoubleBuilderArrayDeserOn JDK 17 the saving is 47 bytes per double. Smile showed +21% deser and +30% ser in a separate run. The existing benchmarks didn't change.
#359 replaces this engine from 3.3/3.4 on, so this is for 2.x and 3.1/3.2.
Benchmark commands (use a disposable checkout)
cc @stevenschlansker @cowtowncoder