ManualNameMatchPerfTest.java
package perf;
import java.util.*;
import tools.jackson.core.*;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.sym.PropertyNameMatcher;
import tools.jackson.core.util.Named;
/**
* Manually run micro-benchmark comparing the two-call
* {@code nextNameMatch()} + {@code nextToken()} sequence against the fused
* {@code nextNameMatchAndToken()}, over both byte- and char-backed parsers.
* Approximates what generated per-bean deserializers do: dispatch on the
* match index, then read the value.
*
* @since 3.3
*/
public class ManualNameMatchPerfTest
{
private final static String[] NAMES = new String[] {
"id", "name", "type", "size", "enabled", "score", "label", "flag"
};
private final JsonFactory _factory = new JsonFactory();
private final byte[] _docBytes;
private final String _docString;
private ManualNameMatchPerfTest(int entries) {
StringBuilder sb = new StringBuilder(entries * 100);
sb.append('[');
for (int i = 0; i < entries; ++i) {
if (i > 0) sb.append(',');
sb.append("{\"id\":").append(i)
.append(",\"name\":\"value").append(i & 0xFF).append('"')
.append(",\"type\":\"t").append(i & 7).append('"')
.append(",\"size\":").append(i * 3)
.append(",\"enabled\":").append((i & 1) == 0)
.append(",\"score\":").append(i % 97)
.append(",\"label\":\"L").append(i & 15).append('"')
.append(",\"flag\":").append((i & 3) == 0)
.append('}');
}
sb.append(']');
_docString = sb.toString();
_docBytes = _docString.getBytes(java.nio.charset.StandardCharsets.UTF_8);
}
private PropertyNameMatcher matcher() {
List<Named> names = new ArrayList<>();
for (String n : NAMES) {
names.add(Named.fromString(n));
}
return _factory.constructNameMatcher(names, true);
}
private JsonParser parser(boolean bytes) {
return bytes ? _factory.createParser(ObjectReadContext.empty(), _docBytes)
: _factory.createParser(ObjectReadContext.empty(), _docString);
}
// Two-call sequence: nextNameMatch() then nextToken()
private long readTwoCall(boolean bytes, PropertyNameMatcher m) {
long sum = 0;
try (JsonParser p = parser(bytes)) {
p.nextToken();
while (p.nextToken() == JsonToken.START_OBJECT) {
while (true) {
int ix = p.nextNameMatch(m);
if (ix < 0) {
break;
}
p.nextToken();
sum += _value(p, ix);
}
}
}
return sum;
}
// Fused: nextNameMatchAndToken()
private long readFused(boolean bytes, PropertyNameMatcher m) {
long sum = 0;
try (JsonParser p = parser(bytes)) {
p.nextToken();
while (p.nextToken() == JsonToken.START_OBJECT) {
while (true) {
int ix = p.nextNameMatchAndToken(m);
if (ix < 0) {
break;
}
sum += _value(p, ix);
}
}
}
return sum;
}
// Stand-in for what a generated deserializer does with the match index
private static long _value(JsonParser p, int ix) {
switch (ix) {
case 0: case 3: case 5:
return p.getIntValue();
case 1: case 2: case 6:
return p.getString().length();
default:
return p.getBooleanValue() ? 1 : 0;
}
}
private void run(int rounds, int repsPerRound) throws Exception
{
final PropertyNameMatcher m = matcher();
System.out.printf("Document: %d bytes, %d properties/round%n",
_docBytes.length, repsPerRound);
for (boolean bytes : new boolean[] { true, false }) {
final String desc = bytes ? "UTF8StreamJsonParser (byte[])"
: "ReaderBasedJsonParser (String)";
// warmup, both paths, to get C2 to steady state
long h = 0;
for (int i = 0; i < 20; ++i) {
h += readTwoCall(bytes, m) + readFused(bytes, m);
}
long bestTwo = Long.MAX_VALUE, bestFused = Long.MAX_VALUE;
for (int i = 0; i < rounds; ++i) {
long start = System.nanoTime();
for (int j = 0; j < repsPerRound; ++j) { h += readTwoCall(bytes, m); }
bestTwo = Math.min(bestTwo, System.nanoTime() - start);
start = System.nanoTime();
for (int j = 0; j < repsPerRound; ++j) { h += readFused(bytes, m); }
bestFused = Math.min(bestFused, System.nanoTime() - start);
}
System.out.printf("%n%s [hash 0x%x]%n", desc, h);
System.out.printf(" two-call : %6.2f msecs%n", bestTwo / 1000000.0);
System.out.printf(" fused : %6.2f msecs (%+.1f%%)%n",
bestFused / 1000000.0,
100.0 * (bestFused - bestTwo) / bestTwo);
}
}
public static void main(String[] args) throws Exception {
new ManualNameMatchPerfTest(2000).run(12, 20);
}
}