Skip to content

Commit 1b2a141

Browse files
committed
Reduce object allocations by use int[] instead of Collection<Integer>
[#138]
1 parent 08ddf59 commit 1b2a141

17 files changed

+188
-140
lines changed

src/main/java/io/r2dbc/postgresql/BoundedStatementCache.java

+45-10
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@
2222
import io.r2dbc.postgresql.util.Assert;
2323
import reactor.core.publisher.Mono;
2424
import reactor.util.annotation.Nullable;
25-
import reactor.util.function.Tuple2;
26-
import reactor.util.function.Tuples;
2725

2826
import java.util.ArrayList;
27+
import java.util.Arrays;
2928
import java.util.Collection;
3029
import java.util.Iterator;
3130
import java.util.LinkedHashMap;
@@ -38,7 +37,7 @@
3837
*/
3938
final class BoundedStatementCache implements StatementCache {
4039

41-
private final Map<Tuple2<String, List<Integer>>, String> cache = new LinkedHashMap<>(16, 0.75f, true);
40+
private final Map<CacheKey, String> cache = new LinkedHashMap<>(16, 0.75f, true);
4241

4342
private final Client client;
4443

@@ -58,7 +57,7 @@ public BoundedStatementCache(Client client, int limit) {
5857
public Mono<String> getName(Binding binding, String sql) {
5958
Assert.requireNonNull(binding, "binding must not be null");
6059
Assert.requireNonNull(sql, "sql must not be null");
61-
Tuple2<String, List<Integer>> key = Tuples.of(sql, binding.getParameterTypes());
60+
CacheKey key = new CacheKey(sql, binding.getParameterTypes());
6261
String name = get(key);
6362
if (name != null) {
6463
return Mono.just(name);
@@ -76,7 +75,7 @@ public Mono<String> getName(Binding binding, String sql) {
7675
.then();
7776
});
7877

79-
return closeLastStatement.then(this.parse(sql, binding.getParameterTypes()))
78+
return closeLastStatement.then(parse(sql, binding.getParameterTypes()))
8079
.doOnNext(preparedName -> put(key, preparedName));
8180
}
8281

@@ -101,7 +100,7 @@ Collection<String> getCachedStatementNames() {
101100
* @return statement name by key
102101
*/
103102
@Nullable
104-
private String get(Tuple2<String, List<Integer>> key) {
103+
private String get(CacheKey key) {
105104
synchronized (this.cache) {
106105
return this.cache.get(key);
107106
}
@@ -114,7 +113,7 @@ private String get(Tuple2<String, List<Integer>> key) {
114113
*/
115114
private String getAndRemoveEldest() {
116115
synchronized (this.cache) {
117-
Iterator<Map.Entry<Tuple2<String, List<Integer>>, String>> iterator = this.cache.entrySet().iterator();
116+
Iterator<Map.Entry<CacheKey, String>> iterator = this.cache.entrySet().iterator();
118117
String entry = iterator.next().getValue();
119118
iterator.remove();
120119
return entry;
@@ -124,7 +123,7 @@ private String getAndRemoveEldest() {
124123
/**
125124
* Synchronized cache access: Store prepared statement.
126125
*/
127-
private void put(Tuple2<String, List<Integer>> key, String preparedName) {
126+
private void put(CacheKey key, String preparedName) {
128127
synchronized (this.cache) {
129128
this.cache.put(key, preparedName);
130129
}
@@ -151,8 +150,8 @@ public String toString() {
151150
'}';
152151
}
153152

154-
private Mono<String> parse(String sql, List<Integer> types) {
155-
String name = String.format("S_%d", this.counter.getAndIncrement());
153+
private Mono<String> parse(String sql, int[] types) {
154+
String name = "S_" + this.counter.getAndIncrement();
156155

157156
ExceptionFactory factory = ExceptionFactory.withSql(name);
158157
return ExtendedQueryMessageFlow
@@ -161,4 +160,40 @@ private Mono<String> parse(String sql, List<Integer> types) {
161160
.then(Mono.just(name))
162161
.cache();
163162
}
163+
164+
static class CacheKey {
165+
166+
String sql;
167+
168+
int[] parameterTypes;
169+
170+
public CacheKey(String sql, int[] parameterTypes) {
171+
this.sql = sql;
172+
this.parameterTypes = parameterTypes;
173+
}
174+
175+
@Override
176+
public boolean equals(Object o) {
177+
if (this == o) {
178+
return true;
179+
}
180+
if (!(o instanceof CacheKey)) {
181+
return false;
182+
}
183+
184+
CacheKey cacheKey = (CacheKey) o;
185+
186+
if (this.sql != null ? !this.sql.equals(cacheKey.sql) : cacheKey.sql != null) {
187+
return false;
188+
}
189+
return Arrays.equals(this.parameterTypes, cacheKey.parameterTypes);
190+
}
191+
192+
@Override
193+
public int hashCode() {
194+
int result = this.sql != null ? this.sql.hashCode() : 0;
195+
result = 31 * result + Arrays.hashCode(this.parameterTypes);
196+
return result;
197+
}
198+
}
164199
}

src/main/java/io/r2dbc/postgresql/DefaultPortalNameSupplier.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ private DefaultPortalNameSupplier() {
3131

3232
@Override
3333
public String get() {
34-
return String.format("B_%d", COUNTER.getAndIncrement());
34+
return "B_%d" + COUNTER.getAndIncrement();
3535
}
3636

3737
}

src/main/java/io/r2dbc/postgresql/IndefiniteStatementCache.java

+33-11
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,16 @@
2121
import io.r2dbc.postgresql.client.ExtendedQueryMessageFlow;
2222
import io.r2dbc.postgresql.util.Assert;
2323
import reactor.core.publisher.Mono;
24-
import reactor.util.function.Tuple2;
25-
import reactor.util.function.Tuples;
2624

27-
import java.util.HashMap;
28-
import java.util.List;
25+
import java.util.Arrays;
2926
import java.util.Map;
27+
import java.util.TreeMap;
28+
import java.util.concurrent.ConcurrentHashMap;
3029
import java.util.concurrent.atomic.AtomicInteger;
3130

3231
final class IndefiniteStatementCache implements StatementCache {
3332

34-
private final Map<Tuple2<String, List<Integer>>, Mono<String>> cache = new HashMap<>();
33+
private final Map<String, Map<int[], Mono<String>>> cache = new ConcurrentHashMap<>();
3534

3635
private final Client client;
3736

@@ -45,11 +44,35 @@ final class IndefiniteStatementCache implements StatementCache {
4544
public Mono<String> getName(Binding binding, String sql) {
4645
Assert.requireNonNull(binding, "binding must not be null");
4746
Assert.requireNonNull(sql, "sql must not be null");
47+
Map<int[], Mono<String>> typedMap = this.cache.computeIfAbsent(sql, ignore -> new TreeMap<>((o1, o2) -> {
4848

49-
synchronized (this.cache) {
50-
return this.cache.computeIfAbsent(Tuples.of(sql, binding.getParameterTypes()),
51-
tuple -> this.parse(tuple.getT1(), tuple.getT2()));
49+
if (Arrays.equals(o1, o2)) {
50+
return 0;
51+
}
52+
53+
if (o1.length != o2.length) {
54+
return o1.length - o2.length;
55+
}
56+
57+
for (int i = 0; i < o1.length; i++) {
58+
59+
int cmp = Integer.compare(o1[i], o2[i]);
60+
61+
if (cmp != 0) {
62+
return cmp;
63+
}
64+
}
65+
66+
return 0;
67+
}));
68+
69+
Mono<String> mono = typedMap.get(binding.getParameterTypes());
70+
if (mono == null) {
71+
mono = parse(sql, binding.getParameterTypes());
72+
typedMap.put(binding.getParameterTypes(), mono);
5273
}
74+
75+
return mono;
5376
}
5477

5578
@Override
@@ -61,8 +84,8 @@ public String toString() {
6184
'}';
6285
}
6386

64-
private Mono<String> parse(String sql, List<Integer> types) {
65-
String name = String.format("S_%d", this.counter.getAndIncrement());
87+
private Mono<String> parse(String sql, int[] types) {
88+
String name = "S_" + this.counter.getAndIncrement();
6689

6790
ExceptionFactory factory = ExceptionFactory.withSql(name);
6891
return ExtendedQueryMessageFlow
@@ -71,5 +94,4 @@ private Mono<String> parse(String sql, List<Integer> types) {
7194
.then(Mono.just(name))
7295
.cache();
7396
}
74-
7597
}

src/main/java/io/r2dbc/postgresql/PostgresqlColumnMetadata.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public boolean equals(Object o) {
7373

7474
@Override
7575
public Class<?> getJavaType() {
76-
return codecs.preferredType(this.nativeType, this.format);
76+
return this.codecs.preferredType(this.nativeType, this.format);
7777
}
7878

7979
@Override
@@ -106,9 +106,6 @@ public String toString() {
106106
}
107107

108108
static PostgresqlColumnMetadata toColumnMetadata(Codecs codecs, Field field) {
109-
Assert.requireNonNull(codecs, "codecs must not be null");
110-
Assert.requireNonNull(field, "field must not be null");
111-
112109
return new PostgresqlColumnMetadata(codecs, field);
113110
}
114111

src/main/java/io/r2dbc/postgresql/PostgresqlResult.java

+31-18
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import io.r2dbc.postgresql.message.backend.CommandComplete;
2424
import io.r2dbc.postgresql.message.backend.DataRow;
2525
import io.r2dbc.postgresql.message.backend.EmptyQueryResponse;
26+
import io.r2dbc.postgresql.message.backend.ErrorResponse;
2627
import io.r2dbc.postgresql.message.backend.PortalSuspended;
2728
import io.r2dbc.postgresql.message.backend.RowDescription;
2829
import io.r2dbc.postgresql.util.Assert;
@@ -31,6 +32,7 @@
3132
import io.r2dbc.spi.RowMetadata;
3233
import reactor.core.publisher.Flux;
3334
import reactor.core.publisher.Mono;
35+
import reactor.core.publisher.SynchronousSink;
3436

3537
import java.util.function.BiFunction;
3638
import java.util.function.Predicate;
@@ -54,39 +56,52 @@ final class PostgresqlResult extends AbstractReferenceCounted implements io.r2db
5456

5557
private volatile RowDescription rowDescription;
5658

57-
PostgresqlResult(ConnectionContext context, Flux<BackendMessage> messages, ExceptionFactory factory) {
58-
this.context = Assert.requireNonNull(context, "context must not be null");
59-
this.messages = Assert.requireNonNull(messages, "messages must not be null");
60-
this.factory = Assert.requireNonNull(factory, "factory must not be null");
59+
private PostgresqlResult(ConnectionContext context, Flux<BackendMessage> messages, ExceptionFactory factory) {
60+
this.context = context;
61+
this.messages = messages;
62+
this.factory = factory;
6163
}
6264

6365
@Override
66+
@SuppressWarnings({"rawtypes", "unchecked"})
6467
public Mono<Integer> getRowsUpdated() {
6568

6669
return this.messages
67-
.handle(this.factory::handleErrorResponse)
68-
.doOnNext(ReferenceCountUtil::release)
69-
.ofType(CommandComplete.class)
70-
.singleOrEmpty()
71-
.handle((commandComplete, sink) -> {
72-
Integer rowCount = commandComplete.getRows();
73-
if (rowCount != null) {
74-
sink.next(rowCount);
75-
} else {
76-
sink.complete();
70+
.<Integer>handle((message, sink) -> {
71+
72+
if (message instanceof ErrorResponse) {
73+
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
74+
return;
7775
}
78-
});
76+
77+
if (message instanceof DataRow) {
78+
((DataRow) message).release();
79+
}
80+
81+
if (message instanceof CommandComplete) {
82+
83+
Integer rowCount = ((CommandComplete) message).getRows();
84+
if (rowCount != null) {
85+
sink.next(rowCount);
86+
}
87+
}
88+
}).singleOrEmpty();
7989
}
8090

8191
@Override
92+
@SuppressWarnings({"rawtypes", "unchecked"})
8293
public <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> f) {
8394
Assert.requireNonNull(f, "f must not be null");
8495

8596
return this.messages.takeUntil(TAKE_UNTIL)
86-
.handle(this.factory::handleErrorResponse)
8797
.handle((message, sink) -> {
8898

8999
try {
100+
if (message instanceof ErrorResponse) {
101+
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
102+
return;
103+
}
104+
90105
if (message instanceof RowDescription) {
91106
this.rowDescription = (RowDescription) message;
92107
this.metadata = PostgresqlRowMetadata.toRowMetadata(this.context.getCodecs(), (RowDescription) message);
@@ -95,8 +110,6 @@ public <T> Flux<T> map(BiFunction<Row, RowMetadata, ? extends T> f) {
95110

96111
if (message instanceof DataRow) {
97112
PostgresqlRow row = PostgresqlRow.toRow(this.context, (DataRow) message, this.rowDescription);
98-
99-
100113
sink.next(f.apply(row, this.metadata));
101114
}
102115

0 commit comments

Comments
 (0)