Skip to content

Commit 422ce09

Browse files
committed
Reduce object allocations by use int[] instead of Collection<Integer>
[#138]
1 parent 68e7817 commit 422ce09

14 files changed

+135
-100
lines changed

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 = this.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-16
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.r2dbc.postgresql.message.backend.CommandComplete;
2525
import io.r2dbc.postgresql.message.backend.DataRow;
2626
import io.r2dbc.postgresql.message.backend.EmptyQueryResponse;
27+
import io.r2dbc.postgresql.message.backend.ErrorResponse;
2728
import io.r2dbc.postgresql.message.backend.PortalSuspended;
2829
import io.r2dbc.postgresql.message.backend.RowDescription;
2930
import io.r2dbc.postgresql.util.Assert;
@@ -32,6 +33,7 @@
3233
import io.r2dbc.spi.RowMetadata;
3334
import reactor.core.publisher.Flux;
3435
import reactor.core.publisher.Mono;
36+
import reactor.core.publisher.SynchronousSink;
3537

3638
import java.util.function.BiFunction;
3739
import java.util.function.Predicate;
@@ -55,39 +57,52 @@ final class PostgresqlResult extends AbstractReferenceCounted implements io.r2db
5557

5658
private volatile RowDescription rowDescription;
5759

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

6466
@Override
67+
@SuppressWarnings({"rawtypes", "unchecked"})
6568
public Mono<Integer> getRowsUpdated() {
6669

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

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

8697
return this.messages.takeUntil(TAKE_UNTIL)
87-
.handle(this.factory::handleErrorResponse)
8898
.handle((message, sink) -> {
8999

90100
try {
101+
if (message instanceof ErrorResponse) {
102+
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
103+
return;
104+
}
105+
91106
if (message instanceof RowDescription) {
92107
this.rowDescription = (RowDescription) message;
93108
this.metadata = PostgresqlRowMetadata.toRowMetadata(this.codecs, (RowDescription) message);

src/main/java/io/r2dbc/postgresql/client/Binding.java

+38-5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.r2dbc.postgresql.message.Format;
2121
import io.r2dbc.postgresql.util.Assert;
2222
import org.reactivestreams.Publisher;
23+
import reactor.core.publisher.Flux;
2324
import reactor.core.publisher.Mono;
2425

2526
import java.util.ArrayList;
@@ -39,6 +40,8 @@ public final class Binding {
3940

4041
private final List<Parameter> parameters;
4142

43+
private final int[] types;
44+
4245
/**
4346
* Create a new instance.
4447
*
@@ -47,6 +50,7 @@ public final class Binding {
4750
public Binding(int expectedSize) {
4851
this.expectedSize = expectedSize;
4952
this.parameters = new ArrayList<>(Collections.nCopies(expectedSize, UNSPECIFIED));
53+
this.types = new int[expectedSize];
5054
}
5155

5256
/**
@@ -57,15 +61,15 @@ public Binding(int expectedSize) {
5761
* @return this {@link Binding}
5862
* @throws IllegalArgumentException if {@code index} or {@code parameter} is {@code null}
5963
*/
60-
public Binding add(Integer index, Parameter parameter) {
61-
Assert.requireNonNull(index, "index must not be null");
64+
public Binding add(int index, Parameter parameter) {
6265
Assert.requireNonNull(parameter, "parameter must not be null");
6366

6467
if (index >= this.expectedSize) {
6568
throw new IndexOutOfBoundsException(String.format("Binding index %d when only %d parameters are expected", index, this.expectedSize));
6669
}
6770

6871
this.parameters.set(index, parameter);
72+
this.types[index] = parameter.getType();
6973

7074
return this;
7175
}
@@ -97,8 +101,15 @@ public List<Format> getParameterFormats() {
97101
*
98102
* @return the types of the parameters in the binding
99103
*/
100-
public List<Integer> getParameterTypes() {
101-
return getTransformedParameters(Parameter::getType);
104+
public int[] getParameterTypes() {
105+
106+
for (int i = 0; i < this.parameters.size(); i++) {
107+
Parameter parameter = this.parameters.get(i);
108+
if (parameter == UNSPECIFIED) {
109+
throw new IllegalStateException(String.format("No parameter specified for index %d", i));
110+
}
111+
}
112+
return this.types;
102113
}
103114

104115
/**
@@ -110,6 +121,10 @@ public List<Publisher<? extends ByteBuf>> getParameterValues() {
110121
return getTransformedParameters(Parameter::getValue);
111122
}
112123

124+
Flux<Publisher<? extends ByteBuf>> parameterValues() {
125+
return Flux.fromIterable(this.parameters).map(Parameter::getValue);
126+
}
127+
113128
@Override
114129
public int hashCode() {
115130
return Objects.hash(this.parameters);
@@ -119,6 +134,10 @@ public boolean isEmpty() {
119134
return this.parameters.isEmpty();
120135
}
121136

137+
public int size() {
138+
return this.parameters.size();
139+
}
140+
122141
@Override
123142
public String toString() {
124143
return "Binding{" +
@@ -140,14 +159,28 @@ public void validate() {
140159
}
141160

142161
private <T> List<T> getTransformedParameters(Function<Parameter, T> transformer) {
143-
List<T> transformed = new ArrayList<>(this.parameters.size());
162+
163+
if (this.parameters.isEmpty()) {
164+
return Collections.emptyList();
165+
}
166+
167+
List<T> transformed = null;
144168

145169
for (int i = 0; i < this.parameters.size(); i++) {
146170
Parameter parameter = this.parameters.get(i);
147171
if (parameter == UNSPECIFIED) {
148172
throw new IllegalStateException(String.format("No parameter specified for index %d", i));
149173
}
150174

175+
if (transformed == null) {
176+
if (this.parameters.size() == 1) {
177+
return Collections.singletonList(transformer.apply(parameter));
178+
}
179+
180+
transformed = new ArrayList<>(this.parameters.size());
181+
}
182+
183+
151184
transformed.add(transformer.apply(parameter));
152185
}
153186

src/main/java/io/r2dbc/postgresql/client/ExtendedQueryMessageFlow.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939

4040
import java.util.Collection;
4141
import java.util.Collections;
42-
import java.util.List;
4342
import java.util.regex.Pattern;
4443

4544
import static io.r2dbc.postgresql.message.frontend.Execute.NO_LIMIT;
@@ -91,7 +90,7 @@ public static Flux<BackendMessage> execute(Publisher<Binding> bindings, Client c
9190
* @return the messages received in response to this exchange
9291
* @throws IllegalArgumentException if {@code client}, {@code name}, {@code query}, or {@code types} is {@code null}
9392
*/
94-
public static Flux<BackendMessage> parse(Client client, String name, String query, List<Integer> types) {
93+
public static Flux<BackendMessage> parse(Client client, String name, String query, int[] types) {
9594
Assert.requireNonNull(client, "client must not be null");
9695
Assert.requireNonNull(name, "name must not be null");
9796
Assert.requireNonNull(query, "query must not be null");
@@ -154,7 +153,7 @@ private static Flux<FrontendMessage> toBindFlow(Binding binding, PortalNameSuppl
154153
.flatMapMany(values -> {
155154
Bind bind = new Bind(portal, binding.getParameterFormats(), values, resultFormat(forceBinary), statementName);
156155

157-
return Flux.just(bind, new Describe(portal, PORTAL), new Execute(portal, NO_LIMIT), new Close(portal, PORTAL));
156+
return Flux.<FrontendMessage>just(bind, new Describe(portal, PORTAL), new Execute(portal, NO_LIMIT), new Close(portal, PORTAL));
158157
}).doOnSubscribe(ignore -> QueryLogger.logQuery(query));
159158
}
160159

src/main/java/io/r2dbc/postgresql/message/frontend/Parse.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import org.reactivestreams.Publisher;
2323
import reactor.core.publisher.Mono;
2424

25-
import java.util.List;
25+
import java.util.Arrays;
2626
import java.util.Objects;
2727

2828
import static io.r2dbc.postgresql.message.frontend.FrontendMessageUtils.writeByte;
@@ -49,7 +49,7 @@ public final class Parse implements FrontendMessage {
4949

5050
private final String name;
5151

52-
private final List<Integer> parameters;
52+
private final int[] parameters;
5353

5454
private final String query;
5555

@@ -63,7 +63,7 @@ public final class Parse implements FrontendMessage {
6363
* @see #UNNAMED_STATEMENT
6464
* @see #UNSPECIFIED
6565
*/
66-
public Parse(String name, List<Integer> parameters, String query) {
66+
public Parse(String name, int[] parameters, String query) {
6767
this.name = Assert.requireNonNull(name, "name must not be null");
6868
this.parameters = Assert.requireNonNull(parameters, "parameters must not be null");
6969
this.query = Assert.requireNonNull(query, "query must not be null");
@@ -81,8 +81,10 @@ public Publisher<ByteBuf> encode(ByteBufAllocator byteBufAllocator) {
8181
writeCStringUTF8(out, this.name);
8282
writeCStringUTF8(out, this.query);
8383

84-
writeShort(out, this.parameters.size());
85-
this.parameters.forEach(parameter -> writeInt(out, parameter));
84+
writeShort(out, this.parameters.length);
85+
for (int parameter : this.parameters) {
86+
writeInt(out, parameter);
87+
}
8688

8789
return writeSize(out);
8890
});
@@ -98,7 +100,7 @@ public boolean equals(Object o) {
98100
}
99101
Parse that = (Parse) o;
100102
return Objects.equals(this.name, that.name) &&
101-
Objects.equals(this.parameters, that.parameters) &&
103+
Arrays.equals(this.parameters, that.parameters) &&
102104
Objects.equals(this.query, that.query);
103105
}
104106

@@ -111,7 +113,7 @@ public int hashCode() {
111113
public String toString() {
112114
return "Parse{" +
113115
"name='" + this.name + '\'' +
114-
", parameters=" + this.parameters +
116+
", parameters=" + Arrays.toString(this.parameters) +
115117
", query='" + this.query + '\'' +
116118
'}';
117119
}

0 commit comments

Comments
 (0)