From 3a6f4bcbdbc64d0a23e6afc5519d94199fc99f77 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 7 Aug 2018 15:02:19 +0200 Subject: [PATCH] #79 - Add benchmark for typical EntityReader. --- benchmark/commons/pom.xml | 43 +++ .../convert/TypicalEntityReaderBenchmark.java | 282 ++++++++++++++++++ .../commons/convert/MyDataClass.kt | 23 ++ benchmark/pom.xml | 1 + 4 files changed, 349 insertions(+) create mode 100644 benchmark/commons/pom.xml create mode 100644 benchmark/commons/src/main/java/org/springframework/data/microbenchmark/commons/convert/TypicalEntityReaderBenchmark.java create mode 100644 benchmark/commons/src/main/kotlin/org/springframework/data/microbenchmark/commons/convert/MyDataClass.kt diff --git a/benchmark/commons/pom.xml b/benchmark/commons/pom.xml new file mode 100644 index 0000000..10c0a65 --- /dev/null +++ b/benchmark/commons/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + + org.springframework.data + spring-data-benchmark-parent + 2.1.0.BUILD-SNAPSHOT + + + spring-data-benchmark-commons + + Spring Data Benchmarks - Commons Microbenchmarks + + + + + ${project.groupId} + spring-data-benchmark-support + + + + ${project.groupId} + spring-data-commons + + + + org.jetbrains.kotlin + kotlin-stdlib-jdk8 + ${kotlin} + + + + org.jetbrains.kotlin + kotlin-reflect + ${kotlin} + + + + + diff --git a/benchmark/commons/src/main/java/org/springframework/data/microbenchmark/commons/convert/TypicalEntityReaderBenchmark.java b/benchmark/commons/src/main/java/org/springframework/data/microbenchmark/commons/convert/TypicalEntityReaderBenchmark.java new file mode 100644 index 0000000..a805e1f --- /dev/null +++ b/benchmark/commons/src/main/java/org/springframework/data/microbenchmark/commons/convert/TypicalEntityReaderBenchmark.java @@ -0,0 +1,282 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.microbenchmark.commons.convert; + +import lombok.Data; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.openjdk.jmh.annotations.Benchmark; +import org.springframework.core.convert.ConversionService; +import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.annotation.AccessType; +import org.springframework.data.annotation.AccessType.Type; +import org.springframework.data.convert.CustomConversions; +import org.springframework.data.convert.CustomConversions.StoreConversions; +import org.springframework.data.convert.EntityInstantiator; +import org.springframework.data.convert.EntityInstantiators; +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.PreferredConstructor.Parameter; +import org.springframework.data.mapping.context.AbstractMappingContext; +import org.springframework.data.mapping.context.MappingContext; +import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty; +import org.springframework.data.mapping.model.BasicPersistentEntity; +import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.mapping.model.ParameterValueProvider; +import org.springframework.data.mapping.model.Property; +import org.springframework.data.mapping.model.PropertyValueProvider; +import org.springframework.data.mapping.model.SimpleTypeHolder; +import org.springframework.data.microbenchmark.common.AbstractMicrobenchmark; +import org.springframework.data.util.TypeInformation; + +/** + * Benchmark for a typical converter that reads entities. + * + * @author Mark Paluch + */ +public class TypicalEntityReaderBenchmark extends AbstractMicrobenchmark { + + private final MyMappingContext context = new MyMappingContext(); + private final EntityInstantiators instantiators = new EntityInstantiators(); + private final ConversionService conversionService = DefaultConversionService.getSharedInstance(); + private final CustomConversions customConversions = new CustomConversions(StoreConversions.NONE, + Collections.emptyList()); + + private final Map simpleEntityData = new HashMap<>(); + + /** + * Prepare and pre-initialize to remove initialization overhead from measurement. + */ + public TypicalEntityReaderBenchmark() { + + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(SimpleEntity.class)); + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(SimpleAccessibleEntityPropertyAccess.class)); + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(SimpleAccessibleEntityFieldAccess.class)); + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(SimpleEntityWithConstructor.class)); + + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(MyDataClass.class)); + instantiators.getInstantiatorFor(context.getRequiredPersistentEntity(MyDataClassWithDefaulting.class)); + + simpleEntityData.put("firstname", "Walter"); + simpleEntityData.put("lastname", "White"); + } + + @Benchmark + public Object simpleEntityReflectivePropertyAccess() { + return read(simpleEntityData, SimpleEntity.class, false); + } + + @Benchmark + public Object simpleEntityReflectivePropertyAccessWithCustomConversionRegistry() { + return read(simpleEntityData, SimpleEntity.class, true); + } + + @Benchmark + public Object simpleEntityGeneratedPropertyAccess() { + return read(simpleEntityData, SimpleAccessibleEntityFieldAccess.class, false); + } + + @Benchmark + public Object simpleEntityGeneratedFieldAccess() { + return read(simpleEntityData, SimpleAccessibleEntityFieldAccess.class, false); + } + + @Benchmark + public Object simpleEntityConstructorArgsCreation() { + return read(simpleEntityData, SimpleEntityWithConstructor.class, true); + } + + @Benchmark + public Object kotlinDataClass() { + return read(simpleEntityData, MyDataClass.class, false); + } + + @Benchmark + public Object kotlinDataClassWithDefaulting() { + return read(simpleEntityData, MyDataClassWithDefaulting.class, false); + } + + static class SimpleEntity { + + String firstname; + String lastname; + } + + @AccessType(Type.PROPERTY) + @Data + public static class SimpleAccessibleEntityPropertyAccess { + + String firstname; + String lastname; + } + + @Data + public static class SimpleAccessibleEntityFieldAccess { + + public String firstname; + public String lastname; + } + + static class SimpleEntityWithConstructor { + + final String firstname; + final String lastname; + + public SimpleEntityWithConstructor(String firstname, String lastname) { + this.firstname = firstname; + this.lastname = lastname; + } + } + + /** + * Typical code used to read entities in {@link org.springframework.data.convert.EntityReader}. + * + * @param data + * @param classToRead + * @param queryCustomConversions {@literal true} to call {@link CustomConversions#hasCustomReadTarget(Class, Class)}. + * @return + */ + @SuppressWarnings("unchecked") + private Object read(Map data, Class classToRead, boolean queryCustomConversions) { + + if (queryCustomConversions) { + customConversions.hasCustomReadTarget(Map.class, classToRead); + } + + MyPersistentEntity persistentEntity = context.getRequiredPersistentEntity(classToRead); + + PropertyValueProvider valueProvider = new PropertyValueProvider() { + + @Override + public T getPropertyValue(MyPersistentProperty property) { + return (T) getValue(data, property.getName(), property.getType(), queryCustomConversions); + } + }; + + ParameterValueProvider provider = new ParameterValueProvider() { + @Override + public T getParameterValue(Parameter parameter) { + return (T) getValue(data, parameter.getName(), parameter.getType().getType(), queryCustomConversions); + } + }; + + EntityInstantiator instantiator = instantiators.getInstantiatorFor(persistentEntity); + Object instance = instantiator.createInstance(persistentEntity, provider); + + PersistentPropertyAccessor accessor = new ConvertingPropertyAccessor<>( + persistentEntity.getPropertyAccessor(instance), conversionService); + + readProperties(data, persistentEntity, valueProvider, accessor); + + return accessor.getBean(); + } + + private void readProperties(Map data, MyPersistentEntity persistentEntity, + PropertyValueProvider valueProvider, PersistentPropertyAccessor accessor) { + + for (MyPersistentProperty prop : persistentEntity) { + + if (prop.isAssociation() && !persistentEntity.isConstructorArgument(prop)) { + continue; + } + + // We skip the id property since it was already set + + if (persistentEntity.isIdProperty(prop)) { + continue; + } + + if (persistentEntity.isConstructorArgument(prop) || !data.containsKey(persistentEntity.getName())) { + continue; + } + + accessor.setProperty(prop, valueProvider.getPropertyValue(prop)); + } + } + + private Object getValue(Map data, String name, Class type, boolean queryCustomConversions) { + + Object value = data.get(name); + + if (queryCustomConversions && value != null) { + customConversions.hasCustomReadTarget(value.getClass(), type); + } + + return value; + } + + /** + * Minimal {@link MappingContext}. + */ + static class MyMappingContext extends AbstractMappingContext, MyPersistentProperty> { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation) + */ + @Override + protected MyPersistentEntity createPersistentEntity(TypeInformation typeInformation) { + return new MyPersistentEntity(typeInformation); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder) + */ + @Override + protected MyPersistentProperty createPersistentProperty(Property property, MyPersistentEntity owner, + SimpleTypeHolder simpleTypeHolder) { + return new MyPersistentProperty(property, owner, simpleTypeHolder); + } + } + + /** + * Minimal {@link PersistentProperty}. + */ + static class MyPersistentProperty extends AnnotationBasedPersistentProperty { + + MyPersistentProperty(Property property, PersistentEntity owner, + SimpleTypeHolder simpleTypeHolder) { + super(property, owner, simpleTypeHolder); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation() + */ + @Override + protected Association createAssociation() { + return null; + } + } + + /** + * Minimal {@link PersistentEntity}. + * + * @param + */ + static class MyPersistentEntity extends BasicPersistentEntity { + + MyPersistentEntity(TypeInformation information) { + super(information); + } + } +} diff --git a/benchmark/commons/src/main/kotlin/org/springframework/data/microbenchmark/commons/convert/MyDataClass.kt b/benchmark/commons/src/main/kotlin/org/springframework/data/microbenchmark/commons/convert/MyDataClass.kt new file mode 100644 index 0000000..441a4ca --- /dev/null +++ b/benchmark/commons/src/main/kotlin/org/springframework/data/microbenchmark/commons/convert/MyDataClass.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.microbenchmark.commons.convert + +/** + * @author Mark Paluch + */ +data class MyDataClass(val firstname: String, val lastname: String) + +data class MyDataClassWithDefaulting(val firstname: String, val lastname: String, val foo: Int = 42) diff --git a/benchmark/pom.xml b/benchmark/pom.xml index 4c01d8f..52bbe6c 100644 --- a/benchmark/pom.xml +++ b/benchmark/pom.xml @@ -19,6 +19,7 @@ support + commons mongodb