Skip to content

Commit

Permalink
Merge branch 'drekbour-jmh'
Browse files Browse the repository at this point in the history
  • Loading branch information
mkarneim committed Mar 10, 2019
2 parents 2b578f3 + 2725755 commit 0615ca7
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 3 deletions.
27 changes: 24 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@
*
* To build the PojoBuilder:
* ./gradlew build
*
* To collect performance numbers:
* ./gradlew jmh
*
* To publish a new release of PojoBuilder to Sonatype OSS Maven Repo:
* ./gradlew clean uploadArchives
*
*/

task wrapper(type: Wrapper) { gradleVersion = '4.5.1' }

buildscript {
repositories { mavenCentral() }
dependencies { classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1' }
repositories {
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'org.kt3k.gradle.plugin:coveralls-gradle-plugin:2.0.1'
classpath 'me.champeau.gradle:jmh-gradle-plugin:0.4.8'
}
}

if (!project.hasProperty('ossrhUsername') || !project.hasProperty('ossrhPassword')) {
Expand All @@ -33,6 +41,7 @@ apply plugin: 'maven'
apply plugin: 'signing'
apply plugin: 'jacoco'
apply plugin: 'com.github.kt3k.coveralls'
apply plugin: 'me.champeau.gradle.jmh'

group = 'net.karneim'
archivesBaseName = "pojobuilder"
Expand Down Expand Up @@ -63,6 +72,11 @@ eclipse {
}
}

jmh {
// https://github.com/melix/jmh-gradle-plugin
includeTests = true // Allows to include test sources into generate JMH jar
}

tasks.withType(JavaCompile) { options.encoding = "UTF-8" }

sourceSets {
Expand All @@ -71,9 +85,16 @@ sourceSets {
srcDir 'src/testdata/java'
srcDir 'src/test/java'
}
resources {
srcDir 'src/jar/resources' // Allow src/jmh to use @GeneratePojoBuilder
}
}
jmh {
sourceCompatibility = 1.8
}
}


jar { from 'src/jar/resources' }

task dependenciesJar(type: Jar, dependsOn:['compileJava']) {
Expand Down
67 changes: 67 additions & 0 deletions src/jmh/java/net/karneim/pojobuilder/Book.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package net.karneim.pojobuilder;

import java.util.Optional;
import java.util.function.Supplier;

/**
* Little bit of everything, for use with <a href="">JMH</a> benchmarks.
*/
public class Book {

public final String title;
public final String author;
public final int words;
public final int pages;
public final Optional<String> publisher;

private Book(String title, String author, int words, int pages, Optional<String> publisher) {
this.title = title;
this.author = author;
this.words = words;
this.pages = pages;
this.publisher = publisher;
}

// Define builders of varying complexities

@GeneratePojoBuilder(
withCopyMethod = true,
withBuilderInterface = Supplier.class
)
public @interface Builder {
}

@Builder
public static Book book1(String title, String author, int words, int pages, Optional<String> publisher) {
return new Book(title, author, words, pages, publisher);
}

@Builder
@GeneratePojoBuilder(
withOptionalProperties = Optional.class,
withName = "*OptionalBuilder"
)
public static Book book2(String title, String author, int words, int pages, Optional<String> publisher) {
return new Book(title, author, words, pages, publisher);
}

@Builder
@GeneratePojoBuilder(
withBuilderProperties = true,
withName = "*PropBuilder"
)
public static Book book3(String title, String author, int words, int pages, Optional<String> publisher) {
return new Book(title, author, words, pages, publisher);
}

@Builder
@GeneratePojoBuilder(
withOptionalProperties = Optional.class,
withBuilderProperties = true,
withName = "*OptionalPropBuilder"
)
public static Book book4(String title, String author, int words, int pages, Optional<String> publisher) {
return new Book(title, author, words, pages, publisher);
}

}
249 changes: 249 additions & 0 deletions src/jmh/java/net/karneim/pojobuilder/PojobuilderPerformance.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package net.karneim.pojobuilder;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@Warmup(time = 10, timeUnit = TimeUnit.SECONDS, batchSize = 100_000)
@Measurement(time = 10, timeUnit = TimeUnit.SECONDS, iterations = 1, batchSize = 100_000)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Fork(1)
@State(Scope.Thread)
public class PojobuilderPerformance {

private BookBuilder builderTemplate;
private Book pojoTemplate;

private String title;
private String author;
private int words;
private int pages;
private String publisher;

private Optional<String> titleO;
private Optional<String> authorO;
private Optional<Integer> wordsO;
private Optional<Integer> pagesO;
private Optional<String> publisherO;

private Supplier<String> titleS;
private Supplier<String> authorS;
private Supplier<Integer> wordsS;
private Supplier<Integer> pagesS;
private Supplier<String> publisherS;

@Setup
// Always initialise test material under @Setup
public void createTemplates() {
title = "A Fire Upon The Deep";
author = "Vernor Vinge";
pages = 613;
words = 50_000;
publisher = "Tor";

titleO = Optional.of(title);
authorO = Optional.of(author);
pagesO = Optional.of(pages);
wordsO = Optional.of(words);
publisherO = Optional.of(publisher);

titleS = () -> title;
authorS = () -> author;
wordsS = () -> words;
pagesS = () -> pages;
publisherS = () -> publisher;

builderTemplate = new BookBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisherO);

pojoTemplate = builderTemplate.get();
}

/**
* Baseline, nothing should be faster than this!
*/
@Benchmark
public Book constructManually() {
return Book.book1(title, author, words, pages, publisherO);
}

/**
* PojoBuilder used within one compilation scope. Counter to many JMH docs,
* this is actually a common scenario for PB.
*/
@Benchmark
public Book constructViaBuilder_private() {
return new BookBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisherO)
.get();
}

/**
* PojoBuilder being passed between methods.
*/
@Benchmark
public Book constructViaBuilder_shared(Blackhole bh) {
BookBuilder builder = new BookBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisherO);
bh.consume(builder); // This stops the argument being shortcut by the JIT
return builder.get();
}

@State(Scope.Benchmark)
public static class LocalBuilder {
ThreadLocal<BookBuilder> localBuilder = ThreadLocal.withInitial(BookBuilder::new);
}

/**
* PojoBuilder instance being held in a ThreadLocal. Be *very* certain no fields can bleed from one call to another if doing this!
*/
@Benchmark
public Book constructViaBuilder_threadlocal(LocalBuilder state) {
return state.localBuilder.get()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisherO)
.get();
}

/**
* PojoBuilder copy-from-existing-then-amend being passed between methods.
*/
@Benchmark
public Book constructViaCopyMethod_shared(Blackhole bh) {
BookBuilder builder = new BookBuilder().copy(pojoTemplate);
bh.consume(builder); // This stops the argument being shortcut by the JIT
return builder.get();
}

/**
* PojoBuilder copy-from-existing-then-amend used within one compilation scope.
*/
@Benchmark
public Book constructViaCopyMethod_private() {
return new BookBuilder().copy(pojoTemplate).get();
}

/**
* PojoBuilder clone-builder-then-amend being passed between methods.
*/
@Benchmark
public Book constructViaButMethod_shared(Blackhole bh) {
BookBuilder builder = builderTemplate.but();
bh.consume(builder); // This stops the argument being shortcut by the JIT
return builder.get();
}

/**
* PojoBuilder clone-builder-then-amend used within one compilation scope.
*/
@Benchmark
public Book constructViaButMethod_private() {
return builderTemplate.but().get();
}

@Benchmark
public Book constructViaOptionalBuilder_withRealProperties() {
return new BookOptionalBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisher)
.get();
}

@Benchmark
public Book constructViaOptionalBuilder_withOptionalProperties() {
return new BookOptionalBuilder()
.withAuthor(authorO)
.withTitle(titleO)
.withPages(pagesO)
.withWords(wordsO)
.withPublisher(publisherO)
.get();
}

@Benchmark
public Book constructViaSupplierBuilder_withSuppliedProperties() {
return new BookPropBuilder()
.withAuthor(authorS)
.withTitle(titleS)
.withPages(pagesS)
.withWords(wordsS)
.withPublisher(() -> publisherO)
.get();
}

@Benchmark
public Book constructViaSupplierBuilder_withRealProperties() {
return new BookPropBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisherO)
.get();
}

@Benchmark
public Book constructViaOptionalSupplierBuilder_withRealProperties() {
return new BookOptionalPropBuilder()
.withAuthor(author)
.withTitle(title)
.withPages(pages)
.withWords(words)
.withPublisher(publisher)
.get();
}

@Benchmark
public Book constructViaOptionalSupplierBuilder_withOptionalProperties() {
return new BookOptionalPropBuilder()
.withAuthor(authorO)
.withTitle(titleO)
.withPages(pagesO)
.withWords(wordsO)
.withPublisher(publisherO)
.get();
}

@Benchmark
public Book constructViaOptionalSupplierBuilder_withSuppliedProperties() {
return new BookOptionalPropBuilder()
.withAuthor(authorS)
.withTitle(titleS)
.withPages(pagesS)
.withWords(wordsS)
.withPublisher(publisherS)
.get();
}

}

0 comments on commit 0615ca7

Please sign in to comment.