Skip to content

Commit b4d08c1

Browse files
authored
Merge pull request #6 from carterkozak/ckozak/benchmark_arbitrary_field_names
Add benchmarks for JSON deserialization including randomized map keys
2 parents 199aaae + a94d718 commit b4d08c1

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.fasterxml.jackson.perf.json;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.core.JsonFactory;
5+
import com.fasterxml.jackson.core.JsonFactoryBuilder;
6+
import com.fasterxml.jackson.core.JsonParser;
7+
import com.fasterxml.jackson.core.TSFBuilder;
8+
import com.fasterxml.jackson.core.type.TypeReference;
9+
import com.fasterxml.jackson.databind.DeserializationFeature;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.fasterxml.jackson.databind.ObjectReader;
12+
import com.fasterxml.jackson.databind.json.JsonMapper;
13+
import org.openjdk.jmh.annotations.Benchmark;
14+
import org.openjdk.jmh.annotations.BenchmarkMode;
15+
import org.openjdk.jmh.annotations.Mode;
16+
import org.openjdk.jmh.annotations.OutputTimeUnit;
17+
import org.openjdk.jmh.annotations.Param;
18+
import org.openjdk.jmh.annotations.Scope;
19+
import org.openjdk.jmh.annotations.Setup;
20+
import org.openjdk.jmh.annotations.State;
21+
22+
import java.io.ByteArrayInputStream;
23+
import java.io.IOException;
24+
import java.io.InputStreamReader;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.Map;
27+
import java.util.concurrent.ThreadLocalRandom;
28+
import java.util.concurrent.TimeUnit;
29+
import java.util.function.Supplier;
30+
31+
@State(Scope.Benchmark)
32+
@BenchmarkMode(Mode.AverageTime)
33+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
34+
public class JsonArbitraryFieldNameBenchmark {
35+
36+
public enum FactoryMode {
37+
DEFAULT() {
38+
@Override
39+
<F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
40+
return factory;
41+
}
42+
},
43+
NO_INTERN() {
44+
@Override
45+
<F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
46+
return factory.disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
47+
}
48+
},
49+
NO_CANONICALIZE() {
50+
@Override
51+
<F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory) {
52+
return factory.disable(JsonFactory.Feature.CANONICALIZE_FIELD_NAMES);
53+
}
54+
};
55+
56+
abstract <F extends JsonFactory, B extends TSFBuilder<F, B>> B apply(B factory);
57+
}
58+
59+
/**
60+
* Ideally we would not generate inputs within the measured component of the benchmark.
61+
*/
62+
public enum InputType {
63+
INPUT_STREAM() {
64+
@Override
65+
JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException {
66+
return factory.createParser(new ByteArrayInputStream(jsonSupplier.get().getBytes(StandardCharsets.UTF_8)));
67+
}
68+
},
69+
READER() {
70+
@Override
71+
JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException {
72+
// Instead of using 'new StringReader(jsonSupplier.get())', we construct an InputStreamReader
73+
// to more closely match overhead of INPUT_STREAM for comparison.
74+
return factory.createParser(new InputStreamReader(
75+
new ByteArrayInputStream(jsonSupplier.get().getBytes(StandardCharsets.UTF_8)),
76+
StandardCharsets.UTF_8));
77+
}
78+
};
79+
80+
abstract JsonParser create(JsonFactory factory, Supplier<String> jsonSupplier) throws IOException;
81+
}
82+
83+
public enum InputShape {
84+
RANDOM_KEY_MAP(
85+
new TypeReference<Map<String, Boolean>>() {},
86+
() -> "{\"" + ThreadLocalRandom.current().nextInt() + "\":true}"),
87+
BEAN_WITH_RANDOM_KEY_MAP(
88+
new TypeReference<SimpleClass>() {},
89+
() -> "{\"fieldWithMap\":{\"" + ThreadLocalRandom.current().nextInt()
90+
+ "\":true},\"stringOne\":\"a\",\"stringTwo\":\"a\",\"stringThree\":\"a\"}");
91+
92+
private final TypeReference<?> typereference;
93+
private final Supplier<String> jsonSupplier;
94+
InputShape(TypeReference<?> typereference, Supplier<String> jsonSupplier) {
95+
this.typereference = typereference;
96+
this.jsonSupplier = jsonSupplier;
97+
}
98+
}
99+
100+
@Param
101+
public InputShape shape;
102+
103+
@Param
104+
public InputType type;
105+
106+
@Param
107+
public FactoryMode mode;
108+
109+
private JsonFactory factory;
110+
private ObjectReader reader;
111+
112+
@Setup
113+
public void setup() {
114+
factory = mode.apply(new JsonFactoryBuilder()).build();
115+
ObjectMapper mapper = JsonMapper.builder(factory)
116+
// Use FAIL_ON_UNKNOWN_PROPERTIES to ensure the benchmark inputs are valid
117+
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
118+
.build();
119+
reader = mapper.readerFor(shape.typereference);
120+
}
121+
122+
@Benchmark
123+
public Object parse() throws IOException {
124+
try (JsonParser parser = type.create(factory, shape.jsonSupplier)) {
125+
return reader.readValue(parser);
126+
}
127+
}
128+
129+
/**
130+
* This type primarily exists to wrap a map, but has additional
131+
* fields to cover a mix of reused and arbitrary json keys.
132+
*/
133+
public static final class SimpleClass {
134+
@JsonProperty("fieldWithMap")
135+
public Map<String, Boolean> fieldWithMap;
136+
@JsonProperty("stringOne")
137+
public String stringOne;
138+
@JsonProperty("stringTwo")
139+
public String stringTwo;
140+
@JsonProperty("stringThree")
141+
public String stringThree;
142+
}
143+
}

0 commit comments

Comments
 (0)