Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions blackbird/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ Following things are optimized:

* For serialization (POJOs to JSON):
* Getter methods are inlined using generated lambdas instead of reflection
* Serializers for small number of 'primitive' types (`int`, `long`, `boolean`, `String`) are replaced with lambda-specialized generators, instead of getting delegated to `JsonSerializer`s
* Serializers for small number of 'primitive' types (`int`, `long`, `double`, `boolean`, `String`) are replaced with lambda-specialized generators, instead of getting delegated to `JsonSerializer`s
* For deserialization (JSON to POJOs):
* Calls to default (no-argument) constructors are lambda-fied instead of using reflection
* Calls to @JsonCreate factory methods and constructors with arguments get a slightly less efficient lambda based implementation
- This is new in Blackbird; Afterburner never did this
* Setter methods are called using lambdas instead of reflection
* Deserializers for small number of 'primitive' types (`int`, `long`, `boolean`, `String`) are replaced with lambda-specialized parsers, instead of getting delegated to `JsonDeserializer`s
* Deserializers for small number of 'primitive' types (`int`, `long`, `double`, `boolean`, `String`) are replaced with lambda-specialized parsers, instead of getting delegated to `JsonDeserializer`s

### ... and what is not?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
Expand Down Expand Up @@ -43,6 +44,11 @@ public abstract class BaseBenchmark {
byte[] classicBeansJson;
ObjectWriter classicBeanWriter;
ObjectReader classicBeanReader;
DoubleBean[] doubleBeans;
byte[] doubleBeansBytes;
ObjectReader doubleBeanReader;
ObjectWriter doubleBeanWriter;
TokenBuffer doubleBeansBuffer;

public static void main(final String[] args) throws RunnerException {
final Options options = new OptionsBuilder()
Expand Down Expand Up @@ -71,6 +77,15 @@ public void setup() throws Exception {
classicBean.setUp();
classicBeans = Collections.nCopies(1000, classicBean).toArray(new ClassicBean[0]);
classicBeansJson = classicBeanWriter.writeValueAsBytes(classicBeans);

doubleBeans = IntStream.range(0, 1000)
.mapToObj(i -> DoubleBean.random(random))
.toArray(DoubleBean[]::new);
doubleBeansBytes = mapper.writeValueAsBytes(doubleBeans);
doubleBeanReader = mapper.readerFor(DoubleBean[].class);
doubleBeanWriter = mapper.writerFor(DoubleBean[].class);
doubleBeansBuffer = new TokenBuffer(mapper, false);
doubleBeanWriter.writeValue(doubleBeansBuffer, doubleBeans);
}

@Benchmark
Expand All @@ -93,6 +108,34 @@ public BeanWithBuilder[] builderArrayDeser() throws Exception {
return mapper.readValue(beansBytes, BeanWithBuilder[].class);
}

@Benchmark
public byte[] doubleBeanArraySer() throws Exception {
return mapper.writeValueAsBytes(doubleBeans);
}

@Benchmark
public DoubleBean[] doubleBeanArrayDeser() throws Exception {
return mapper.readValue(doubleBeansBytes, DoubleBean[].class);
}

// Through a buffer, as with `convertValue`, so without number parsing or formatting
@Benchmark
public TokenBuffer doubleBeanArrayBufferSer() throws Exception {
TokenBuffer buffer = new TokenBuffer(mapper, false);
doubleBeanWriter.writeValue(buffer, doubleBeans);
return buffer;
}

@Benchmark
public DoubleBean[] doubleBeanArrayBufferDeser() throws Exception {
return doubleBeanReader.readValue(doubleBeansBuffer.asParser());
}

@Benchmark
public DoubleBeanWithBuilder[] doubleBuilderArrayDeser() throws Exception {
return mapper.readValue(doubleBeansBytes, DoubleBeanWithBuilder[].class);
}

@Benchmark
public void classicMediaItemSer(final Blackhole bh) throws Exception {
mediaItemWriter.writeValue(new NopOutputStream(bh), MediaItem.SAMPLE);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.fasterxml.jackson.module.blackbird;

import java.util.Random;

// Mostly primitive doubles, with values as short as most real ones
public class DoubleBean {
private int id;
private double latitude;
private double longitude;
private double altitude;
private double speed;
private double heading;
private double accuracy;

public int getId() { return id; }
public void setId(int id) { this.id = id; }

public double getLatitude() { return latitude; }
public void setLatitude(double latitude) { this.latitude = latitude; }

public double getLongitude() { return longitude; }
public void setLongitude(double longitude) { this.longitude = longitude; }

public double getAltitude() { return altitude; }
public void setAltitude(double altitude) { this.altitude = altitude; }

public double getSpeed() { return speed; }
public void setSpeed(double speed) { this.speed = speed; }

public double getHeading() { return heading; }
public void setHeading(double heading) { this.heading = heading; }

public double getAccuracy() { return accuracy; }
public void setAccuracy(double accuracy) { this.accuracy = accuracy; }

static DoubleBean random(Random random) {
final DoubleBean result = new DoubleBean();
result.setId(random.nextInt());
result.setLatitude(round(random.nextDouble() * 180.0 - 90.0, 1e6));
result.setLongitude(round(random.nextDouble() * 360.0 - 180.0, 1e6));
result.setAltitude(round(random.nextDouble() * 9000.0, 1e2));
result.setSpeed(round(random.nextDouble() * 300.0, 1e2));
result.setHeading(round(random.nextDouble() * 360.0, 1e2));
result.setAccuracy(round(random.nextDouble() * 50.0, 1e2));
return result;
}

private static double round(double value, double scale) {
return Math.round(value * scale) / scale;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.fasterxml.jackson.module.blackbird;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;

@JsonDeserialize(builder = DoubleBeanWithBuilder.Builder.class)
public class DoubleBeanWithBuilder extends DoubleBean {
private DoubleBeanWithBuilder() { }

public static class Builder {
private final DoubleBeanWithBuilder bean = new DoubleBeanWithBuilder();

public Builder withId(int id) {
bean.setId(id);
return this;
}

public Builder withLatitude(double latitude) {
bean.setLatitude(latitude);
return this;
}

public Builder withLongitude(double longitude) {
bean.setLongitude(longitude);
return this;
}

public Builder withAltitude(double altitude) {
bean.setAltitude(altitude);
return this;
}

public Builder withSpeed(double speed) {
bean.setSpeed(speed);
return this;
}

public Builder withHeading(double heading) {
bean.setHeading(heading);
return this;
}

public Builder withAccuracy(double accuracy) {
bean.setAccuracy(accuracy);
return this;
}

public DoubleBeanWithBuilder build() {
return bean;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.ObjDoubleConsumer;
import java.util.function.ObjIntConsumer;
import java.util.function.ObjLongConsumer;
import java.util.function.UnaryOperator;
Expand Down Expand Up @@ -182,7 +183,8 @@ private void nextProperty(SettableBeanProperty prop,
}
type = ((AnnotatedMethod) member).getRawParameterType(0);
// Only these get a generated setter, so don't check (or load) anything for others
if (type.isPrimitive() && (type != Integer.TYPE) && (type != Long.TYPE) && (type != Boolean.TYPE)) {
if (type.isPrimitive() && (type != Integer.TYPE) && (type != Long.TYPE)
&& (type != Double.TYPE) && (type != Boolean.TYPE)) {
return;
}
// A generated setter that would fail on every call (like one taking a
Expand Down Expand Up @@ -232,6 +234,15 @@ private void nextProperty(SettableBeanProperty prop,
: (bean, v) -> { consumer.accept(bean, v); return null; };
newProps.add(new SettableLongProperty(prop, consumer,
thunk, receiverType, forBuilder));
} else if (type == Double.TYPE) {
ObjDoubleBiFunction returning = createThunk(lookup, ObjDoubleBiFunction.class, double.class, setter);
ObjDoubleConsumer<Object> consumer = (returning == null)
? createConsumer(lookup, ObjDoubleConsumer.class, double.class, setter)
: (bean, v) -> returning.apply(bean, v);
ObjDoubleBiFunction thunk = !forBuilder ? null : (returning != null) ? returning
: (bean, v) -> { consumer.accept(bean, v); return null; };
newProps.add(new SettableDoubleProperty(prop, consumer,
thunk, receiverType, forBuilder));
} else if (type == Boolean.TYPE) {
ObjBooleanBiFunction returning = createThunk(lookup, ObjBooleanBiFunction.class, boolean.class, setter);
ObjBooleanConsumer consumer = (returning == null)
Expand Down Expand Up @@ -356,6 +367,11 @@ public interface ObjLongBiFunction {
Object apply(Object bean, long value);
}

@FunctionalInterface
public interface ObjDoubleBiFunction {
Object apply(Object bean, double value);
}

@FunctionalInterface
public interface ObjBooleanBiFunction {
Object apply(Object bean, boolean value);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.fasterxml.jackson.module.blackbird.deser;

import java.io.IOException;
import java.util.function.ObjDoubleConsumer;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.module.blackbird.deser.BBDeserializerModifier.ObjDoubleBiFunction;

final class SettableDoubleProperty
extends OptimizedSettableBeanProperty<SettableDoubleProperty>
{
private static final long serialVersionUID = 1L;
private ObjDoubleConsumer<Object> _optimizedSetter;
private ObjDoubleBiFunction _optimizedSetterAndReturn;

public SettableDoubleProperty(SettableBeanProperty src, ObjDoubleConsumer<Object> optimizedSetter,
ObjDoubleBiFunction optimizedSetterAndReturn, Class<?> receiverType, boolean forBuilder)
{
super(src, receiverType, forBuilder);
_optimizedSetter = optimizedSetter;
_optimizedSetterAndReturn = optimizedSetterAndReturn;
}

@Override
protected SettableBeanProperty withDelegate(SettableBeanProperty del) {
return new SettableDoubleProperty(del, _optimizedSetter, _optimizedSetterAndReturn, _receiverType, _forBuilder);
}

/*
/**********************************************************************
/* Deserialization
/**********************************************************************
*/

// Only a float token is read directly, as the default deserializer reads it
// (without coercion checks); anything else, including integers, Strings (like
// "NaN") and nulls, is left to the delegate and its coercion rules

@Override
public void deserializeAndSet(JsonParser p, DeserializationContext ctxt,
Object bean) throws IOException
{
if (!p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
delegate.deserializeAndSet(p, ctxt, bean);
return;
}
final double v = p.getDoubleValue();
try {
_optimizedSetter.accept(bean, v);
} catch (Throwable e) {
throw _reportProblem(p, e);
}
}

@Override
public Object deserializeSetAndReturn(JsonParser p,
DeserializationContext ctxt, Object instance) throws IOException
{
if ((_optimizedSetterAndReturn != null) && _receiverType.isInstance(instance)
&& p.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
return _setAndReturn(p, instance, p.getDoubleValue());
}
return delegate.deserializeSetAndReturn(p, ctxt, instance);
}

@Override
public void set(Object bean, Object value) throws IOException {
// the delegate skips a null, or reports it; and anything else (another type,
// which reflection may widen or reject) or a builder the generated setter
// can't take is reported, or set, as reflection does
if (!(value instanceof Double) || (_forBuilder && !_receiverType.isInstance(bean))) {
delegate.set(bean, value);
return;
}
// not optimal (due to boxing), but better than using reflection:
final double v = ((Double) value).doubleValue();
try {
_optimizedSetter.accept(bean, v);
} catch (Throwable e) {
throw _reportProblem(null, e);
}
}

@Override
public Object setAndReturn(Object instance, Object value) throws IOException {
// a setter only reflection may call, anything else (null, or another type that
// reflection may widen or reject), or an instance the generated setter can't take,
// is left to the delegate
if ((_optimizedSetterAndReturn == null) || !(value instanceof Double)
|| !_receiverType.isInstance(instance)) {
return delegate.setAndReturn(instance, value);
}
// not optimal (due to boxing), but better than using reflection:
return _setAndReturn(null, instance, ((Double) value).doubleValue());
}

private Object _setAndReturn(JsonParser p, Object instance, double v) throws IOException {
try {
Object result = _optimizedSetterAndReturn.apply(instance, v);
return (result == null) ? instance : result;
} catch (Throwable e) {
throw _reportProblem(p, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.UnaryOperator;
Expand Down Expand Up @@ -150,6 +151,22 @@ protected void createProperty(ListIterator<BeanPropertyWriter> it, Lookup lookup
getter.type())
.getTarget().invokeExact();
it.set(new LongPropertyWriter(bpw, accessor, null));
} else if (type == Double.TYPE) {
// `DoubleSerializer` writes a type id for non-finite values, so a property
// with a type serializer (which no standard resolver gives a primitive)
// is left to `BeanPropertyWriter`
if (bpw.getTypeSerializer() != null) {
return;
}
ToDoubleFunction<Object> accessor = (ToDoubleFunction<Object>) LambdaMetafactory.metafactory(
lookup,
"applyAsDouble",
methodType(ToDoubleFunction.class),
methodType(double.class, Object.class),
getter,
getter.type())
.getTarget().invokeExact();
it.set(new DoublePropertyWriter(bpw, accessor, null));
} else if (type == Boolean.TYPE) {
ToBooleanFunction accessor = (ToBooleanFunction) LambdaMetafactory.metafactory(
lookup,
Expand Down
Loading
Loading