Skip to content

Add StringReader input benchmark #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 20, 2023
Merged
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@
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.OptionsBuilder;
import org.openjdk.jmh.runner.options.TimeValue;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
Expand Down Expand Up @@ -51,7 +59,8 @@ <F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
<F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
return factory.disable(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES);
}
};
},
;

abstract <F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory);
}
Expand All @@ -62,38 +71,88 @@ <F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
public enum InputType {
INPUT_STREAM() {
@Override
JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException {
return factory.createParser(new ByteArrayInputStream(jsonSupplier.get().getBytes(StandardCharsets.UTF_8)));
JsonParser create(JsonFactory factory, Supplier<byte[]> bytesSupplier) throws IOException {
return factory.createParser(new ByteArrayInputStream(bytesSupplier.get()));
}
},
READER() {
@Override
JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException {
// Instead of using 'new StringReader(jsonSupplier.get())', we construct an InputStreamReader
JsonParser create(JsonFactory factory, Supplier<byte[]> bytesSupplier) throws IOException {
// Instead of using 'new StringReader(bytesSupplier.get())', we construct an InputStreamReader
// to more closely match overhead of INPUT_STREAM for comparison.
return factory.createParser(new InputStreamReader(
new ByteArrayInputStream(jsonSupplier.get().getBytes(StandardCharsets.UTF_8)),
new ByteArrayInputStream(bytesSupplier.get()),
StandardCharsets.UTF_8));
}
};
},
CHAR_READER() {
@Override
JsonParser create(JsonFactory factory, Supplier<byte[]> jsonSupplier) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.wrap(jsonSupplier.get());
CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
CharBufferReader charBufferReader = new CharBufferReader(charBuffer);
return factory.createParser(charBufferReader);
}
},
STRING_READER() {
@Override
JsonParser create(JsonFactory factory, Supplier<byte[]> jsonSupplier) throws IOException {
StringReader reader = new StringReader(new String(jsonSupplier.get(), Charset.forName("UTF-8")));
return factory.createParser(reader);
}
},
;

abstract JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException;
abstract JsonParser create(JsonFactory factory, Supplier<byte[]> jsonSupplier) throws IOException;
}

public enum InputShape {
KEY_MAP(
new TypeReference<Map<String, Boolean>>() {
},
() -> "{\"key\":true}"),
RANDOM_KEY_MAP(
new TypeReference<Map<String, Boolean>>() {},
new TypeReference<Map<String, Boolean>>() {
},
() -> "{\"" + ThreadLocalRandom.current().nextInt() + "\":true}"),
BEAN_WITH_RANDOM_KEY_MAP(
new TypeReference<SimpleClass>() {},
new TypeReference<SimpleClass>() {
},
() -> "{\"fieldWithMap\":{\"" + ThreadLocalRandom.current().nextInt()
+ "\":true},\"stringOne\":\"a\",\"stringTwo\":\"a\",\"stringThree\":\"a\"}");
+ "\":true},\"stringOne\":\"a\",\"stringTwo\":\"a\",\"stringThree\":\"a\"}"),
BEAN_WITH_LARGE_KEY_MAP(
new TypeReference<SimpleClass>() {
},
new Supplier<String>() {
private final String json = generateSimpleInstanceJson(10_000);

@Override
public String get() {
return json;
}
}
),
;

private static String generateSimpleInstanceJson(int n) {
StringBuilder builder = new StringBuilder();
builder.append("{\"fieldWithMap\":{");
for (int i = 0; i < n; i++) {
builder.append("\"").append(i).append("\":").append(i % 2 == 0);
if (i < n-1) {
builder.append(',');
}
}
builder.append("},\"stringOne\":\"a\",\"stringTwo\":\"a\",\"stringThree\":\"a\"}");
return builder.toString();
}

private final TypeReference<?> typereference;
private final Supplier<String> jsonSupplier;
private final Supplier<byte[]> bytesSupplier;

InputShape(TypeReference<?> typereference, Supplier<String> jsonSupplier) {
this.typereference = typereference;
this.jsonSupplier = jsonSupplier;
this.bytesSupplier = () -> jsonSupplier.get().getBytes(StandardCharsets.UTF_8);
}
}

Expand Down Expand Up @@ -121,7 +180,7 @@ public void setup() {

@Benchmark
public Object parse() throws IOException {
try (JsonParser parser = type.create(factory, shape.jsonSupplier)) {
try (JsonParser parser = type.create(factory, shape.bytesSupplier)) {
return reader.readValue(parser);
}
}
Expand All @@ -140,4 +199,78 @@ public static final class SimpleClass {
@JsonProperty("stringThree")
public String stringThree;
}

public static class CharBufferReader extends Reader {
private final CharBuffer charBuffer;

public CharBufferReader(CharBuffer buffer) {
this.charBuffer = buffer.duplicate();
}

@Override
public int read(char[] chars, int off, int len) {
int remaining = this.charBuffer.remaining();
if (remaining <= 0) {
return -1;
}
int length = Math.min(len, remaining);
this.charBuffer.get(chars, off, length);
return length;
}

@Override
public int read() {
if (this.charBuffer.hasRemaining()) {
return this.charBuffer.get();
}
return -1;
}

@Override
public long skip(long n) {
if (n < 0L) {
throw new IllegalArgumentException("number of characters to skip cannot be negative");
}
int skipped = Math.min((int) n, this.charBuffer.remaining());
this.charBuffer.position(this.charBuffer.position() + skipped);
return skipped;
}

@Override
public boolean ready() {
return true;
}

@Override
public boolean markSupported() {
return true;
}

@Override
public void mark(int readAheadLimit) {
this.charBuffer.mark();
}

@Override
public void reset() {
this.charBuffer.reset();
}

@Override
public void close() {
this.charBuffer.position(this.charBuffer.limit());
}
}

public static void main(String[] _args) throws Exception {
new Runner(new OptionsBuilder()
.include(JsonArbitraryFieldNameBenchmark.class.getName())
.warmupIterations(2)
.warmupTime(TimeValue.seconds(5))
.measurementIterations(4)
.measurementTime(TimeValue.seconds(5))
.mode(Mode.AverageTime)
.forks(1)
.build()).run();
}
}