Skip to content

Commit 34426da

Browse files
committed
Add support for Jackson's new ByteBufferFeeder (apple#1711)
Motivation: In 2.14.0, Jackson added support for feeding ByteBuffers directly into the streaming parser engine (where previously only byte arrays would be supported). This changeset incorporates this enhancement into the JacksonStreamingSerializer infrastructure. Modifications: Before this changeset, the ByteArrayParser (and corresponding feeder) were the only choice when using the streaming deserialization infrastructure in Jackson, so the code was able to eaglerly initialize the parser and feeder. Now there is a choice that can be made at runtime which parser might be the best one based on the incoming buffer in the stream. As such, the code now checks the first Buffer and then decides if either the array-backed or the bytebuffer-backed parser should be initalized. This also has the advantage that if no items are emitted on an empty stream, no parser needs to be initialized at all. Note that the code still preserves the runtime "backed by type" checks since (while likely not common but possible) buffers with different backing types can arrive one after another and need to be handled. In this case it is possible that a sub-optimal parser is chosen, but the code optimizes for the likely scenario that all buffers on one stream are backed by the same type. Also a MethodHandle-based runtime check has been added which makes sure that even if a user overrides the Jackson version to an older one on the Classpath the code is still functional. Result: Added support for Jackson's new ByteBufferFeeder and choosing the best strategy at runtime depending on the first buffer type that arrives.
1 parent 97f9a21 commit 34426da

File tree

5 files changed

+277
-74
lines changed

5 files changed

+277
-74
lines changed

servicetalk-benchmarks/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232
implementation project(":servicetalk-http-netty")
3333
implementation project(":servicetalk-transport-netty-internal")
3434
implementation project(":servicetalk-loadbalancer")
35+
implementation project(":servicetalk-data-jackson")
3536
implementation "com.google.code.findbugs:jsr305"
3637
implementation "io.netty:netty-codec-http"
3738
implementation "org.openjdk.jmh:jmh-core"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright © 2022 Apple Inc. and the ServiceTalk project authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.servicetalk.data.jackson;
17+
18+
import com.fasterxml.jackson.core.type.TypeReference;
19+
import io.servicetalk.buffer.api.Buffer;
20+
import io.servicetalk.buffer.netty.BufferAllocators;
21+
import org.openjdk.jmh.annotations.Benchmark;
22+
import org.openjdk.jmh.annotations.BenchmarkMode;
23+
import org.openjdk.jmh.annotations.Fork;
24+
import org.openjdk.jmh.annotations.Measurement;
25+
import org.openjdk.jmh.annotations.Mode;
26+
import org.openjdk.jmh.annotations.Scope;
27+
import org.openjdk.jmh.annotations.State;
28+
import org.openjdk.jmh.annotations.Warmup;
29+
30+
import java.util.Map;
31+
32+
import static io.servicetalk.concurrent.api.Publisher.from;
33+
import static io.servicetalk.data.jackson.JacksonSerializerFactory.JACKSON;
34+
35+
/**
36+
* Performs (de)serialization benchmarks on the JacksonStreamingSerializer.
37+
*
38+
* Benchmark Mode Cnt Score Error Units
39+
* JacksonStreamingSerializerBenchmark.deserializeLargeBackedByArray thrpt 5 114568,190 ± 5174,037 ops/s
40+
* JacksonStreamingSerializerBenchmark.deserializeLargeBackedByDirect thrpt 5 96666,948 ± 4912,809 ops/s
41+
* JacksonStreamingSerializerBenchmark.deserializeMidBackedByArray thrpt 5 527170,664 ± 107330,448 ops/s
42+
* JacksonStreamingSerializerBenchmark.deserializeMidBackedByDirect thrpt 5 420306,213 ± 78607,263 ops/s
43+
* JacksonStreamingSerializerBenchmark.deserializeSmallBackedByArray thrpt 5 1307308,103 ± 128131,629 ops/s
44+
* JacksonStreamingSerializerBenchmark.deserializeSmallBackedByDirect thrpt 5 674688,550 ± 108705,165 ops/s
45+
*
46+
* NOTE/TODO: the different numbers on the small and mid-sized buffers are likely due to higher allocation overhead for
47+
* direct buffers compared to array ones. The benchmark can be modified in the future to refactor the allocation out
48+
* so that it is not accounted for.
49+
*/
50+
@Fork(value = 1)
51+
@State(Scope.Benchmark)
52+
@Warmup(iterations = 5, time = 3)
53+
@Measurement(iterations = 5, time = 3)
54+
@BenchmarkMode(Mode.Throughput)
55+
public class JacksonStreamingSerializerBenchmark {
56+
57+
/**
58+
* Type reference used for deserialization mapping.
59+
*/
60+
private static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE =
61+
new TypeReference<Map<String, Object>>() {};
62+
63+
/**
64+
* Represents a 10-byte JSON object (encoded).
65+
*/
66+
private static final String SMALL_JSON = "{\"a\":true}";
67+
68+
/**
69+
* Represents a 100-byte JSON object (encoded).
70+
*/
71+
private static final String MID_JSON = "{\"_id\":\"6371eb6af678bb79174\",\"a\":false,\"balance\":\"$2,087.12\"," +
72+
"\"picture\":\"https://placehold.it/32x32\"}";
73+
74+
/**
75+
* Represents a 1000-byte JSON object (encoded).
76+
*/
77+
private static final String LARGE_JSON = "{\"_id\":\"6371eaaf236316f8be8b44cc\",\"index\":0,\"guid\":" +
78+
"\"12cdb7e4-b133-4da7-94b3-b1c58c4b3ef6\",\"isActive\":true,\"balance\":\"$3,777.31\",\"picture\":" +
79+
"\"http://placehold.it/32x32\",\"age\":24,\"eyeColor\":\"blue\",\"name\":\"Nikki Randall\"," +
80+
"\"gender\":\"female\",\"company\":\"PROVIDCO\",\"email\":\"[email protected]\",\"phone\":" +
81+
"\"+1 (981) 475-2422\",\"address\":\"914 Butler Street, Eggertsville, Maryland, 5970\",\"about\":" +
82+
"\"Nulla proident velit culpa magna sit duis in in deserunt. Irure consectetur ea veniam quis amet " +
83+
"nulla tempor in esse ipsum. Do aute magna in qui qui dolor adipisicing ipsum nulla deserunt. Labore " +
84+
"voluptate sint laborum adipisicing dolor anim amet pariatur sint. Ad nulla commodo ipsum aliqua " +
85+
"est.\\r\\n\",\"registered\":\"2022-01-01T03:44:39 -01:00\",\"latitude\":61.061004,\"longitude\":38.544479," +
86+
"\"tags\":[\"qui\",\"nulla\",\"cillum\",\"dolor\",\"deserunt\",\"amet\",\"magna\"],\"friends\":[{\"id\":0," +
87+
"\"name\":\"Gibson Benson\"},{\"id\":1,\"name\":\"Marshall Atkins\"},{\"id\":2,\"name\":\"Gabriel Clay\"}," +
88+
"{\"id\":3,\"name\":\"Aguilar\"},{\"id\":4,\"name\":\"Bauer\"}]}";
89+
90+
@Benchmark
91+
public Map<String, Object> deserializeSmallBackedByArray() throws Exception {
92+
return deserialize(BufferAllocators.PREFER_HEAP_ALLOCATOR.fromUtf8(SMALL_JSON));
93+
}
94+
95+
@Benchmark
96+
public Map<String, Object> deserializeSmallBackedByDirect() throws Exception {
97+
return deserialize(BufferAllocators.PREFER_DIRECT_ALLOCATOR.fromUtf8(SMALL_JSON));
98+
}
99+
100+
@Benchmark
101+
public Map<String, Object> deserializeMidBackedByArray() throws Exception {
102+
return deserialize(BufferAllocators.PREFER_HEAP_ALLOCATOR.fromUtf8(MID_JSON));
103+
}
104+
105+
@Benchmark
106+
public Map<String, Object> deserializeMidBackedByDirect() throws Exception {
107+
return deserialize(BufferAllocators.PREFER_DIRECT_ALLOCATOR.fromUtf8(MID_JSON));
108+
}
109+
110+
@Benchmark
111+
public Map<String, Object> deserializeLargeBackedByArray() throws Exception {
112+
return deserialize(BufferAllocators.PREFER_HEAP_ALLOCATOR.fromUtf8(LARGE_JSON));
113+
}
114+
115+
@Benchmark
116+
public Map<String, Object> deserializeLargeBackedByDirect() throws Exception {
117+
return deserialize(BufferAllocators.PREFER_DIRECT_ALLOCATOR.fromUtf8(LARGE_JSON));
118+
}
119+
120+
private static Map<String, Object> deserialize(final Buffer fromBuffer) throws Exception {
121+
return JACKSON
122+
.streamingSerializerDeserializer(MAP_TYPE_REFERENCE)
123+
.deserialize(from(fromBuffer), BufferAllocators.DEFAULT_ALLOCATOR)
124+
.firstOrError()
125+
.toFuture()
126+
.get();
127+
}
128+
129+
}

servicetalk-data-jackson/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation project(":servicetalk-concurrent-internal")
3030
implementation project(":servicetalk-utils-internal")
3131
implementation "com.google.code.findbugs:jsr305"
32+
implementation "org.slf4j:slf4j-api"
3233

3334
testImplementation testFixtures(project(":servicetalk-concurrent-api"))
3435
testImplementation testFixtures(project(":servicetalk-concurrent-internal"))

0 commit comments

Comments
 (0)