Skip to content

Commit 751b091

Browse files
vai-frdmp911de
authored andcommitted
Introduce codec mapping caches to improve encoding and decoding performances.
* Switched the codec list to a thread safe variant to avoid the synchronized blocks. Even though `CopyOnWriteArrayList` is not super performant it should work fine in this context where the list should not be frequently updated. * Switched `mockito-core` to `mockito-junit-jupiter` for Junit 5 support * Refactored the codec registry to use a CodecFinder (default to SPI definition in the classpath) * Provided 2 implementations of the codec finder, one without cache and another with cache * Added a build cache method that will attempt to fill the cache when the codecs are updated. This cannot covers all the cases like the nested arrays, therefore for those type the cache will be filled dynamically on per-request basis * Added microbenchmarks for codec encode and decode using the cache based implementation or not * Disabled unixDomainSocketTest IT when running on Windows [#444][resolves #409]
1 parent 52b8328 commit 751b091

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1040
-96
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
</dependency>
211211
<dependency>
212212
<groupId>org.mockito</groupId>
213-
<artifactId>mockito-core</artifactId>
213+
<artifactId>mockito-junit-jupiter</artifactId>
214214
<version>${mockito.version}</version>
215215
<scope>test</scope>
216216
</dependency>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2021 the original author or 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+
* https://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+
17+
package io.r2dbc.postgresql;
18+
19+
import io.netty.buffer.ByteBufAllocator;
20+
import io.netty.buffer.UnpooledByteBufAllocator;
21+
import io.r2dbc.postgresql.codec.CodecFinderCacheImpl;
22+
import io.r2dbc.postgresql.codec.CodecFinderDefaultImpl;
23+
import io.r2dbc.postgresql.codec.Codecs;
24+
import io.r2dbc.postgresql.codec.DefaultCodecs;
25+
import io.r2dbc.postgresql.util.ByteBufUtils;
26+
import org.junit.platform.commons.annotation.Testable;
27+
import org.openjdk.jmh.annotations.Benchmark;
28+
import org.openjdk.jmh.annotations.BenchmarkMode;
29+
import org.openjdk.jmh.annotations.Mode;
30+
import org.openjdk.jmh.annotations.OutputTimeUnit;
31+
import org.openjdk.jmh.annotations.Param;
32+
import org.openjdk.jmh.annotations.Scope;
33+
import org.openjdk.jmh.annotations.State;
34+
import org.openjdk.jmh.infra.Blackhole;
35+
36+
import java.time.LocalDateTime;
37+
import java.util.concurrent.TimeUnit;
38+
39+
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
40+
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
41+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.FLOAT4;
42+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.FLOAT8;
43+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.FLOAT8_ARRAY;
44+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.INT2;
45+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.INT2_ARRAY;
46+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.INT4;
47+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.INT4_ARRAY;
48+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.TIMESTAMP;
49+
import static io.r2dbc.postgresql.type.PostgresqlObjectId.VARCHAR;
50+
import static io.r2dbc.postgresql.util.TestByteBufAllocator.TEST;
51+
52+
/**
53+
* Benchmarks for codec encoding and decoding using cached enabled or disabled registries.
54+
*/
55+
@BenchmarkMode(Mode.Throughput)
56+
@OutputTimeUnit(TimeUnit.SECONDS)
57+
@Testable
58+
public class CodecRegistryBenchmarks extends BenchmarkSettings {
59+
60+
@State(Scope.Benchmark)
61+
public static class CodecRegistryHolder {
62+
63+
@Param({"10", "100", "1000"})
64+
public int iterations;
65+
66+
final ByteBufAllocator byteBufAllocator = new UnpooledByteBufAllocator(false, true);
67+
68+
DefaultCodecs cacheEnabledRegistry = new DefaultCodecs(byteBufAllocator, false, new CodecFinderCacheImpl());
69+
70+
DefaultCodecs cacheDisabledRegistry = new DefaultCodecs(byteBufAllocator, false, new CodecFinderDefaultImpl());
71+
72+
}
73+
74+
private void decode(Codecs codecs, int iterations, Blackhole voodoo) {
75+
for (int i = 0; i < iterations; i++) {
76+
voodoo.consume(codecs.decode(
77+
TEST.buffer(4).writeInt(200), INT4.getObjectId(), FORMAT_BINARY, Integer.class));
78+
voodoo.consume(codecs.decode(
79+
ByteBufUtils.encode(TEST, "100"), INT2.getObjectId(), FORMAT_TEXT, Short.class));
80+
voodoo.consume(codecs.decode(
81+
ByteBufUtils.encode(TEST, "-125.369"), FLOAT8.getObjectId(), FORMAT_TEXT, Double.class));
82+
voodoo.consume(codecs.decode(
83+
TEST.buffer(4).writeFloat(-65.369f), FLOAT4.getObjectId(), FORMAT_BINARY, Float.class));
84+
voodoo.consume(
85+
codecs.decode(
86+
ByteBufUtils.encode(TEST, "test"),
87+
VARCHAR.getObjectId(),
88+
FORMAT_TEXT,
89+
String.class));
90+
voodoo.consume(
91+
codecs.decode(
92+
ByteBufUtils.encode(TEST, "2018-11-04 15:35:00.847108"),
93+
TIMESTAMP.getObjectId(),
94+
FORMAT_TEXT,
95+
LocalDateTime.class));
96+
voodoo.consume(codecs.decode(ByteBufUtils.encode(TEST, "{100,200}"), INT2_ARRAY.getObjectId(), FORMAT_TEXT, Object.class));
97+
voodoo.consume(codecs.decode(ByteBufUtils.encode(TEST, "{100,200}"), INT4_ARRAY.getObjectId(), FORMAT_TEXT, Object.class));
98+
voodoo.consume(codecs.decode(ByteBufUtils.encode(TEST, "{100.5,200.8}"), FLOAT8_ARRAY.getObjectId(), FORMAT_TEXT, Object.class));
99+
}
100+
}
101+
102+
@Benchmark
103+
public void decodeWithCacheEnabledRegistry(CodecRegistryHolder holder, Blackhole voodoo) {
104+
decode(holder.cacheEnabledRegistry, holder.iterations, voodoo);
105+
}
106+
107+
@Benchmark
108+
public void decodeWithCacheDisabledRegistry(CodecRegistryHolder holder, Blackhole voodoo) {
109+
decode(holder.cacheDisabledRegistry, holder.iterations, voodoo);
110+
}
111+
112+
private void encode(Codecs codecs, int iterations, Blackhole voodoo) {
113+
for (int i = 0; i < iterations; i++) {
114+
voodoo.consume(codecs.encode((short) 12));
115+
voodoo.consume(codecs.encode(35698));
116+
voodoo.consume(codecs.encode(-256.3698));
117+
voodoo.consume(codecs.encode(85.7458f));
118+
voodoo.consume(codecs.encode("A text value"));
119+
voodoo.consume(codecs.encode(LocalDateTime.now()));
120+
voodoo.consume(codecs.encode(new Long[]{100L, 200L}));
121+
voodoo.consume(codecs.encode(new Double[]{100.5, 200.25}));
122+
voodoo.consume(codecs.encodeNull(Integer.class));
123+
voodoo.consume(codecs.encodeNull(String.class));
124+
voodoo.consume(codecs.encodeNull(Double[].class));
125+
}
126+
}
127+
128+
@Benchmark
129+
public void encodeWithCacheEnabledRegistry(CodecRegistryHolder holder, Blackhole voodoo) {
130+
encode(holder.cacheEnabledRegistry, holder.iterations, voodoo);
131+
}
132+
133+
@Benchmark
134+
public void encodeWithCacheDisabledRegistry(CodecRegistryHolder holder, Blackhole voodoo) {
135+
encode(holder.cacheDisabledRegistry, holder.iterations, voodoo);
136+
}
137+
138+
}

src/main/java/io/r2dbc/postgresql/codec/AbstractBinaryCodec.java

+6
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import io.r2dbc.postgresql.util.ByteBufUtils;
2727

2828
import java.nio.charset.StandardCharsets;
29+
import java.util.Collections;
2930
import java.util.Objects;
3031
import java.util.regex.Matcher;
3132
import java.util.regex.Pattern;
@@ -62,6 +63,11 @@ boolean doCanDecode(PostgresqlObjectId type, Format format) {
6263
return BYTEA == type;
6364
}
6465

66+
@Override
67+
public Iterable<PostgresqlObjectId> getDataTypes() {
68+
return Collections.singleton(BYTEA);
69+
}
70+
6571
byte[] decode(Format format, ByteBuf byteBuf) {
6672
if (format == FORMAT_TEXT) {
6773
return decodeFromHex(byteBuf);

src/main/java/io/r2dbc/postgresql/codec/AbstractCodec.java

+9
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@
2626
import reactor.core.publisher.Mono;
2727
import reactor.util.annotation.Nullable;
2828

29+
import java.util.EnumSet;
2930
import java.util.function.Supplier;
3031

3132
import static io.r2dbc.postgresql.client.Parameter.NULL_VALUE;
33+
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
34+
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
3235

3336
/**
3437
* Abstract codec class that provides a basis for all concrete
@@ -95,6 +98,12 @@ public Class<?> type() {
9598
return this.type;
9699
}
97100

101+
@Override
102+
public Iterable<Format> getFormats() {
103+
// Unless overridden all codecs supports both text and binary format
104+
return EnumSet.of(FORMAT_TEXT, FORMAT_BINARY);
105+
}
106+
98107
/**
99108
* Create a {@link Parameter}.
100109
*

src/main/java/io/r2dbc/postgresql/codec/AbstractGeometryCodec.java

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io.r2dbc.postgresql.util.ByteBufUtils;
2626

2727
import java.util.ArrayList;
28+
import java.util.Collections;
2829
import java.util.Iterator;
2930
import java.util.List;
3031

@@ -103,6 +104,11 @@ public Parameter encodeNull() {
103104
return createNull(this.postgresqlObjectId, Format.FORMAT_BINARY);
104105
}
105106

107+
@Override
108+
public Iterable<PostgresqlObjectId> getDataTypes() {
109+
return Collections.singleton(this.postgresqlObjectId);
110+
}
111+
106112
/**
107113
* Create a {@link TokenStream} given {@code content}.
108114
*

src/main/java/io/r2dbc/postgresql/codec/AbstractJsonCodec.java

+11-1
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,17 @@
2121
import io.r2dbc.postgresql.type.PostgresqlObjectId;
2222
import io.r2dbc.postgresql.util.Assert;
2323

24+
import java.util.EnumSet;
25+
import java.util.Set;
26+
2427
import static io.r2dbc.postgresql.message.Format.FORMAT_BINARY;
2528
import static io.r2dbc.postgresql.type.PostgresqlObjectId.JSON;
2629
import static io.r2dbc.postgresql.type.PostgresqlObjectId.JSONB;
2730

2831
abstract class AbstractJsonCodec<T> extends AbstractCodec<T> {
2932

33+
private static final Set<PostgresqlObjectId> SUPPORTED_TYPES = EnumSet.of(JSON, JSONB);
34+
3035
AbstractJsonCodec(Class<T> type) {
3136
super(type);
3237
}
@@ -41,7 +46,12 @@ boolean doCanDecode(PostgresqlObjectId type, Format format) {
4146
Assert.requireNonNull(format, "format must not be null");
4247
Assert.requireNonNull(type, "type must not be null");
4348

44-
return JSONB == type || JSON == type;
49+
return SUPPORTED_TYPES.contains(type);
50+
}
51+
52+
@Override
53+
public Iterable<PostgresqlObjectId> getDataTypes() {
54+
return SUPPORTED_TYPES;
4555
}
4656

4757
}

src/main/java/io/r2dbc/postgresql/codec/AbstractNumericCodec.java

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ T decodeNumber(ByteBuf buffer, PostgresqlObjectId dataType, @Nullable Format for
8787
return potentiallyConvert(number, expectedType, converter);
8888
}
8989

90+
@Override
91+
public Iterable<PostgresqlObjectId> getDataTypes() {
92+
return SUPPORTED_TYPES;
93+
}
9094
/**
9195
* Returns the {@link PostgresqlObjectId} for to identify whether this codec is the default codec.
9296
*

src/main/java/io/r2dbc/postgresql/codec/AbstractTemporalCodec.java

+5
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,9 @@ static <T> T potentiallyConvert(Temporal temporal, Class<T> expectedType, Functi
158158
return expectedType.isInstance(temporal) ? expectedType.cast(temporal) : converter.apply(temporal);
159159
}
160160

161+
@Override
162+
public Iterable<PostgresqlObjectId> getDataTypes() {
163+
return SUPPORTED_TYPES;
164+
}
165+
161166
}

src/main/java/io/r2dbc/postgresql/codec/BigDecimalArrayCodec.java

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.r2dbc.postgresql.util.Assert;
2525

2626
import java.math.BigDecimal;
27+
import java.util.Collections;
2728
import java.util.function.Supplier;
2829

2930
final class BigDecimalArrayCodec extends AbstractArrayCodec<BigDecimal> {
@@ -37,6 +38,11 @@ public Parameter encodeNull() {
3738
return createNull(PostgresqlObjectId.NUMERIC_ARRAY, Format.FORMAT_TEXT);
3839
}
3940

41+
@Override
42+
public Iterable<PostgresqlObjectId> getDataTypes() {
43+
return Collections.singleton(PostgresqlObjectId.NUMERIC_ARRAY);
44+
}
45+
4046
@Override
4147
boolean doCanDecode(PostgresqlObjectId type, Format format) {
4248
Assert.requireNonNull(type, "type must not be null");

src/main/java/io/r2dbc/postgresql/codec/BlobCodec.java

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import reactor.util.annotation.Nullable;
3030

3131
import java.nio.ByteBuffer;
32+
import java.util.Collections;
3233

3334
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
3435
import static io.r2dbc.postgresql.type.PostgresqlObjectId.BYTEA;
@@ -75,6 +76,11 @@ Parameter doEncode(Blob value) {
7576
);
7677
}
7778

79+
@Override
80+
public Iterable<PostgresqlObjectId> getDataTypes() {
81+
return Collections.singleton(BYTEA);
82+
}
83+
7884
private static final class ByteABlob implements Blob {
7985

8086
private final ByteBuf byteBuf;

src/main/java/io/r2dbc/postgresql/codec/BooleanArrayCodec.java

+6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.r2dbc.postgresql.type.PostgresqlObjectId;
2424
import io.r2dbc.postgresql.util.Assert;
2525

26+
import java.util.Collections;
2627
import java.util.function.Supplier;
2728

2829
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
@@ -42,6 +43,11 @@ public Parameter encodeNull() {
4243
return createNull(BOOL_ARRAY, FORMAT_TEXT);
4344
}
4445

46+
@Override
47+
public Iterable<PostgresqlObjectId> getDataTypes() {
48+
return Collections.singleton(BOOL_ARRAY);
49+
}
50+
4551
@Override
4652
Boolean doDecodeBinary(ByteBuf byteBuffer) {
4753
return byteBuffer.readBoolean();

src/main/java/io/r2dbc/postgresql/codec/BooleanCodec.java

+7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import io.r2dbc.postgresql.util.ByteBufUtils;
2626
import reactor.util.annotation.Nullable;
2727

28+
import java.util.Collections;
29+
2830
import static io.r2dbc.postgresql.message.Format.FORMAT_TEXT;
2931
import static io.r2dbc.postgresql.type.PostgresqlObjectId.BOOL;
3032

@@ -42,6 +44,11 @@ public Parameter encodeNull() {
4244
return createNull(BOOL, FORMAT_TEXT);
4345
}
4446

47+
@Override
48+
public Iterable<PostgresqlObjectId> getDataTypes() {
49+
return Collections.singleton(BOOL);
50+
}
51+
4552
@Override
4653
boolean doCanDecode(PostgresqlObjectId type, Format format) {
4754
Assert.requireNonNull(format, "format must not be null");

src/main/java/io/r2dbc/postgresql/codec/ByteCodec.java

+10
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,14 @@ Parameter doEncode(Byte value) {
6262
return this.delegate.doEncode((short) value);
6363
}
6464

65+
@Override
66+
public Iterable<Format> getFormats() {
67+
return this.delegate.getFormats();
68+
}
69+
70+
@Override
71+
public Iterable<PostgresqlObjectId> getDataTypes() {
72+
return this.delegate.getDataTypes();
73+
}
74+
6575
}

src/main/java/io/r2dbc/postgresql/codec/CharacterCodec.java

+10
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,14 @@ Parameter doEncode(Character value) {
6262
return this.delegate.doEncode(value.toString());
6363
}
6464

65+
@Override
66+
public Iterable<Format> getFormats() {
67+
return this.delegate.getFormats();
68+
}
69+
70+
@Override
71+
public Iterable<PostgresqlObjectId> getDataTypes() {
72+
return this.delegate.getDataTypes();
73+
}
74+
6575
}

0 commit comments

Comments
 (0)