DATACASS-399 - Create JMH benchmarks.

Add benchmarks for plain reads and writes without mapping and using object mapping.
This commit is contained in:
Mark Paluch
2017-02-08 17:06:05 +01:00
parent d0982dc119
commit 633b90493f
7 changed files with 658 additions and 0 deletions

View File

@@ -82,6 +82,7 @@
<module>spring-cql</module>
<module>spring-data-cassandra</module>
<module>spring-data-cassandra-distribution</module>
<module>spring-data-cassandra-benchmarks</module>
</modules>
<properties>

View File

@@ -0,0 +1,98 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra-parent</artifactId>
<version>2.0.0.DATACASS-389-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>spring-data-cassandra-benchmarks</artifactId>
<name>Spring Data Cassandra - Benchmarks</name>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-cassandra</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>${jmh.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>${jmh.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<jmh.version>1.17.4</jmh.version>
<uberjar.name>benchmarks</uberjar.name>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>${uberjar.name}</finalName>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- Shading signed JARs will fail without this. http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,32 @@
/*
* Copyright 2017 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.cassandra.benchmarks;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.data.cassandra.mapping.UserDefinedType;
/**
* @author Mark Paluch
*/
@Getter
@RequiredArgsConstructor
@UserDefinedType
public class Address {
final String city, zip;
}

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2017 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.cassandra.benchmarks;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
/**
* Benchmark for {@link CassandraMappingContext}.
*
* @author Mark Paluch
*/
@State(Scope.Benchmark)
public class CassandraMappingContextBenchmark {
@Benchmark
public void measureGetPersistentEntity(Blackhole blackhole) {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(typeName -> null);
blackhole.consume(mappingContext.getPersistentEntity(Address.class));
}
@Benchmark
public void measureGetPersistentEntityWithUdtReference(Blackhole blackhole) {
BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
mappingContext.setUserTypeResolver(typeName -> null);
blackhole.consume(mappingContext.getRequiredPersistentEntity(Customer.class));
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder() //
.include(CassandraMappingContextBenchmark.class.getSimpleName()) //
.forks(1) //
.warmupIterations(5) //
.measurementIterations(10) //
.mode(Mode.AverageTime) //
.timeUnit(TimeUnit.NANOSECONDS) //
.build();
new Runner(opt).run();
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2017 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.cassandra.benchmarks;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.cassandra.mapping.Table;
/**
* @author Mark Paluch
*/
@Data
@Table
public class Customer {
private @Id String id;
private String firstname, lastname;
private Address address;
}

View File

@@ -0,0 +1,231 @@
/*
* Copyright 2017 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.cassandra.benchmarks;
import static java.util.Arrays.*;
import static org.springframework.data.cassandra.benchmarks.MappingCassandraConverterBenchmark.BenchmarkDependencyFactory.*;
import java.lang.reflect.Constructor;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import org.springframework.util.ReflectionUtils;
import com.datastax.driver.core.CodecRegistry;
import com.datastax.driver.core.ColumnDefinitions;
import com.datastax.driver.core.ColumnDefinitions.Definition;
import com.datastax.driver.core.DataType;
import com.datastax.driver.core.ProtocolVersion;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.UserType;
import com.datastax.driver.core.UserType.Field;
import com.datastax.driver.core.querybuilder.QueryBuilder;
/**
* Benchmark for {@link MappingCassandraConverter}.
*
* @author Mark Paluch
*/
@State(Scope.Benchmark)
public class MappingCassandraConverterBenchmark {
private final BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
private final MappingCassandraConverter converter = new MappingCassandraConverter(mappingContext);
private ColumnDefinitions customerColumns;
private UserType addressType;
private ByteBuffer id;
private ByteBuffer firstname;
private ByteBuffer lastname;
private ByteBuffer address;
private Customer customer;
private Customer customerWithMappedUdt;
@Setup
public void beforeBenchmark() throws ReflectiveOperationException {
this.mappingContext.setUserTypeResolver(new BenchmarkUserTypeResolver());
this.mappingContext.getPersistentEntity(Customer.class);
this.mappingContext.getPersistentEntity(Address.class);
this.addressType = createUserType("address",
asList(field("zip", DataType.varchar()), field("city", DataType.varchar())));
this.customerColumns = columnDefinitions(asList(definition("id", DataType.varchar()), //
definition("firstname", DataType.varchar()), //
definition("lastname", DataType.varchar()), //
definition("address", this.addressType) //
));
this.id = encode("my-id", DataType.varchar());
this.firstname = encode("Walter", DataType.varchar());
this.lastname = encode("White", DataType.varchar());
UDTValue udtValue = this.addressType.newValue();
udtValue.setString("zip", "12345");
udtValue.setString("city", "Albuquerque");
this.address = encode(udtValue, this.addressType);
Address address = new Address("12345", "Albuquerque");
this.customer = createCustomer();
Customer customerWithMappedUdt = createCustomer();
customerWithMappedUdt.setAddress(address);
this.customerWithMappedUdt = customerWithMappedUdt;
}
private Customer createCustomer() {
Customer customer = new Customer();
customer.setId("my-id");
customer.setFirstname("Walter");
customer.setLastname("White");
return customer;
}
// Benchmark
public void measureReadRow() throws ReflectiveOperationException {
Row row = createRow(this.customerColumns,
asList(id.duplicate(), this.firstname.duplicate(), this.lastname.duplicate(), null));
this.converter.read(Customer.class, row);
}
// @Benchmark
public void measureReadRowWithUdt() throws ReflectiveOperationException {
Row row = createRow(this.customerColumns,
asList(this.id.duplicate(), this.firstname.duplicate(), this.lastname.duplicate(), this.address.duplicate()));
converter.read(Customer.class, row);
}
@Benchmark
public void measureWriteQuery() throws ReflectiveOperationException {
converter.write(this.customer, QueryBuilder.insertInto("table"));
}
@Benchmark
public void measureWriteRowWithUdt() throws ReflectiveOperationException {
converter.write(this.customerWithMappedUdt, QueryBuilder.insertInto("table"));
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder() //
.include(MappingCassandraConverterBenchmark.class.getSimpleName()) //
.forks(1) //
.warmupIterations(5) //
.measurementIterations(10) //
.mode(Mode.AverageTime) //
.timeUnit(TimeUnit.NANOSECONDS) //
.build();
new Runner(opt).run();
}
class BenchmarkUserTypeResolver implements UserTypeResolver {
@Override
public UserType resolveType(CqlIdentifier typeName) {
return addressType;
}
}
/**
* Factory to create dependencies required for the benchmark.
*/
static class BenchmarkDependencyFactory {
static Row createRow(ColumnDefinitions definitions, List<ByteBuffer> data) throws ReflectiveOperationException {
Class<Row> rowClass = (Class) Class.forName("com.datastax.driver.core.ArrayBackedRow");
Class<?> tokenFactoryClass = Class.forName("com.datastax.driver.core.Token$Factory");
Constructor<Row> constructor = ReflectionUtils.accessibleConstructor(rowClass, ColumnDefinitions.class,
tokenFactoryClass, ProtocolVersion.class, List.class);
return constructor.newInstance(definitions, null, ProtocolVersion.NEWEST_SUPPORTED, data);
}
static Definition definition(String name, DataType type) throws ReflectiveOperationException {
Constructor<Definition> constructor = ReflectionUtils.accessibleConstructor(Definition.class, String.class,
String.class, String.class, DataType.class);
return constructor.newInstance("keyspace", "table", name, type);
}
static ColumnDefinitions columnDefinitions(Collection<Definition> definitions) throws ReflectiveOperationException {
Constructor<ColumnDefinitions> constructor = ReflectionUtils.accessibleConstructor(ColumnDefinitions.class,
Definition[].class, CodecRegistry.class);
return constructor.newInstance(definitions.toArray(new ColumnDefinitions.Definition[0]),
CodecRegistry.DEFAULT_INSTANCE);
}
static Field field(String name, DataType type) throws ReflectiveOperationException {
Class<Field> fieldClass = (Class) Class.forName("com.datastax.driver.core.UserType$Field");
Constructor<Field> constructor = ReflectionUtils.accessibleConstructor(fieldClass, String.class, DataType.class);
return constructor.newInstance(name, type);
}
static UserType createUserType(String name, Collection<Field> fields) throws ReflectiveOperationException {
Constructor<UserType> constructor = ReflectionUtils.accessibleConstructor(UserType.class, String.class,
String.class, Collection.class, ProtocolVersion.class, CodecRegistry.class);
return constructor.newInstance("keyspace", name, fields, ProtocolVersion.NEWEST_SUPPORTED,
CodecRegistry.DEFAULT_INSTANCE);
}
static ByteBuffer encode(Object data, DataType type) throws ReflectiveOperationException {
TypeCodec<Object> objectTypeCodec = CodecRegistry.DEFAULT_INSTANCE.codecFor(type);
return objectTypeCodec.serialize(data, ProtocolVersion.NEWEST_SUPPORTED);
}
}
}

View File

@@ -0,0 +1,195 @@
/*
* Copyright 2017 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.cassandra.benchmarks;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
import io.netty.channel.EventLoopGroup;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.cassandra.core.cql.CqlIdentifier;
import org.springframework.data.cassandra.config.CassandraSessionFactoryBean;
import org.springframework.data.cassandra.config.SchemaAction;
import org.springframework.data.cassandra.convert.MappingCassandraConverter;
import org.springframework.data.cassandra.mapping.BasicCassandraMappingContext;
import org.springframework.data.cassandra.mapping.SimpleUserTypeResolver;
import org.springframework.data.cassandra.mapping.UserTypeResolver;
import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.NettyOptions;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.UDTValue;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Update;
/**
* Benchmark for {@link MappingCassandraConverter} requiring a running Apache Cassandra server on {@code localhost:9042}
* providing a keyspace named {@code example}.
*
* @author Mark Paluch
*/
@State(Scope.Benchmark)
public class MappingCassandraConverterOnlineBenchmark {
private final BasicCassandraMappingContext mappingContext = new BasicCassandraMappingContext();
private final MappingCassandraConverter converter = new MappingCassandraConverter(mappingContext);
private Cluster cluster;
private Session session;
private UserTypeResolver userTypeResolver;
private Customer customerWithMappedUdt;
@Setup
public void beforeBenchmark() throws Exception {
cluster = Cluster.builder().addContactPoint("localhost").withNettyOptions(new NettyOptions() {
public void onClusterClose(EventLoopGroup eventLoopGroup) {
eventLoopGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS).syncUninterruptibly();
}
}).build();
CassandraSessionFactoryBean sessionFactoryBean = new CassandraSessionFactoryBean();
sessionFactoryBean.setKeyspaceName("example");
sessionFactoryBean.setSchemaAction(SchemaAction.RECREATE);
sessionFactoryBean.setCluster(cluster);
sessionFactoryBean.setConverter(converter);
this.userTypeResolver = new SimpleUserTypeResolver(cluster, "example");
this.mappingContext.setUserTypeResolver(userTypeResolver);
this.mappingContext.getPersistentEntity(Customer.class);
this.mappingContext.getPersistentEntity(Address.class);
sessionFactoryBean.afterPropertiesSet();
this.session = sessionFactoryBean.getObject();
UDTValue udtValue = this.userTypeResolver.resolveType(CqlIdentifier.cqlId("address")).newValue();
udtValue.setString("zip", "12345");
udtValue.setString("city", "Albuquerque");
this.session.execute(QueryBuilder.truncate("customer"));
this.session.execute(QueryBuilder.insertInto("customer").value("id", "my-id").value("firstname", "Walter")
.value("lastname", "White").value("address", udtValue));
Address address = new Address("12345", "Albuquerque");
Customer customerWithMappedUdt = createCustomer();
customerWithMappedUdt.setAddress(address);
this.customerWithMappedUdt = customerWithMappedUdt;
}
private Customer createCustomer() {
Customer customer = new Customer();
customer.setId("my-id");
customer.setFirstname("Walter");
customer.setLastname("White");
return customer;
}
@TearDown
public void afterBenchmark() {
this.session.close();
this.cluster.close();
}
@Benchmark
public void measureReadRowPlain(Blackhole blackhole) {
ResultSet rows = this.session.execute(QueryBuilder.select().from("customer").where(eq("id", "my-id")));
Row row = rows.one();
blackhole.consume(row.getString("id"));
blackhole.consume(row.getString("firstname"));
blackhole.consume(row.getString("lastname"));
UDTValue address = row.getUDTValue("address");
blackhole.consume(address.getString("zip"));
blackhole.consume(address.getString("city"));
}
@Benchmark
public void measureReadRowMapped(Blackhole blackhole) {
ResultSet rows = this.session.execute(QueryBuilder.select().from("customer").where(eq("id", "my-id")));
Row row = rows.one();
blackhole.consume(this.converter.read(Customer.class, row));
}
@Benchmark
public void measureWriteRowPlain() {
UDTValue udtValue = this.userTypeResolver.resolveType(CqlIdentifier.cqlId("address")).newValue();
udtValue.setString("zip", "12345");
udtValue.setString("city", "Albuquerque");
Statement statement = QueryBuilder.update("customer") //
.where(eq("id", "my-id")) //
.with(QueryBuilder.set("firstname", "Walter")) //
.and(QueryBuilder.set("lastname", "White")) //
.and(QueryBuilder.set("address", udtValue));
this.session.execute(statement);
}
@Benchmark
public void measureWriteRowMapped() {
Update update = QueryBuilder.update("customer");
this.converter.write(customerWithMappedUdt, update);
this.session.execute(update);
}
public static void main(String[] args) throws Exception {
Options opt = new OptionsBuilder() //
.include(MappingCassandraConverterOnlineBenchmark.class.getSimpleName()) //
.forks(1) //
.warmupIterations(5) //
.measurementIterations(10) //
.mode(Mode.AverageTime) //
.timeUnit(TimeUnit.NANOSECONDS) //
.build();
new Runner(opt).run();
}
}