Skip to content

Commit

Permalink
Reduce object allocations by use int[] instead of Collection<Integer>
Browse files Browse the repository at this point in the history
  • Loading branch information
mp911de committed Nov 25, 2019
1 parent def5d63 commit c6e1d56
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private DefaultPortalNameSupplier() {

@Override
public String get() {
return String.format("B_%d", COUNTER.getAndIncrement());
return "B_%d" + COUNTER.getAndIncrement();
}

}
42 changes: 33 additions & 9 deletions src/main/java/io/r2dbc/postgresql/IndefiniteStatementCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@
import io.r2dbc.postgresql.client.ExtendedQueryMessageFlow;
import io.r2dbc.postgresql.util.Assert;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.util.HashMap;
import java.util.List;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

final class IndefiniteStatementCache implements StatementCache {

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

private final Client client;

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

return this.cache.computeIfAbsent(Tuples.of(sql, binding.getParameterTypes()),
tuple -> this.parse(tuple.getT1(), tuple.getT2()));
if (Arrays.equals(o1, o2)) {
return 0;
}

if (o1.length != o2.length) {
return o1.length - o2.length;
}

for (int i = 0; i < o1.length; i++) {

int cmp = Integer.compare(o1[i], o2[i]);

if (cmp != 0) {
return cmp;
}
}

return 0;
}));

Mono<String> mono = typedMap.get(binding.getParameterTypes());
if (mono == null) {
mono = this.parse(sql, binding.getParameterTypes());
typedMap.put(binding.getParameterTypes(), mono);
}

return mono;
}

@Override
Expand All @@ -59,7 +84,7 @@ public String toString() {
'}';
}

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

ExceptionFactory factory = ExceptionFactory.withSql(name);
Expand All @@ -69,5 +94,4 @@ private Mono<String> parse(String sql, List<Integer> types) {
.then(Mono.just(name))
.cache();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public boolean equals(Object o) {

@Override
public Class<?> getJavaType() {
return codecs.preferredType(this.nativeType, this.format);
return this.codecs.preferredType(this.nativeType, this.format);
}

@Override
Expand Down Expand Up @@ -106,9 +106,6 @@ public String toString() {
}

static PostgresqlColumnMetadata toColumnMetadata(Codecs codecs, Field field) {
Assert.requireNonNull(codecs, "codecs must not be null");
Assert.requireNonNull(field, "field must not be null");

return new PostgresqlColumnMetadata(codecs, field);
}

Expand Down
45 changes: 28 additions & 17 deletions src/main/java/io/r2dbc/postgresql/PostgresqlResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import io.r2dbc.postgresql.message.backend.CommandComplete;
import io.r2dbc.postgresql.message.backend.DataRow;
import io.r2dbc.postgresql.message.backend.EmptyQueryResponse;
import io.r2dbc.postgresql.message.backend.ErrorResponse;
import io.r2dbc.postgresql.message.backend.PortalSuspended;
import io.r2dbc.postgresql.message.backend.RowDescription;
import io.r2dbc.postgresql.util.Assert;
Expand All @@ -29,6 +30,7 @@
import io.r2dbc.spi.RowMetadata;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;

import java.util.function.BiFunction;
import java.util.function.Predicate;
Expand All @@ -52,42 +54,51 @@ final class PostgresqlResult implements io.r2dbc.postgresql.api.PostgresqlResult

private volatile RowDescription rowDescription;

PostgresqlResult(Codecs codecs, Flux<BackendMessage> messages, ExceptionFactory factory) {
this.codecs = Assert.requireNonNull(codecs, "codecs must not be null");
this.messages = Assert.requireNonNull(messages, "messages must not be null");
this.factory = Assert.requireNonNull(factory, "factory must not be null");
private PostgresqlResult(Codecs codecs, Flux<BackendMessage> messages, ExceptionFactory factory) {
this.codecs = codecs;
this.messages = messages;
this.factory = factory;
}

@Override
@SuppressWarnings({"rawtypes", "unchecked"})
public Mono<Integer> getRowsUpdated() {

return this.messages
.handle(this.factory::handleErrorResponse)
.doOnNext(message -> {
.<Integer>handle((message, sink) -> {

if (message instanceof ErrorResponse) {
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
return;
}

if (message instanceof DataRow) {
((DataRow) message).release();
}
})
.ofType(CommandComplete.class)
.singleOrEmpty()
.handle((commandComplete, sink) -> {
Integer rowCount = commandComplete.getRows();
if (rowCount != null) {
sink.next(rowCount);
} else {
sink.complete();

if (message instanceof CommandComplete) {

Integer rowCount = ((CommandComplete) message).getRows();
if (rowCount != null) {
sink.next(rowCount);
}
}
});
}).singleOrEmpty();
}

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

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

if (message instanceof ErrorResponse) {
this.factory.handleErrorResponse(message, (SynchronousSink) sink);
return;
}

if (message instanceof RowDescription) {
this.rowDescription = (RowDescription) message;
this.metadata = PostgresqlRowMetadata.toRowMetadata(this.codecs, (RowDescription) message);
Expand Down
43 changes: 38 additions & 5 deletions src/main/java/io/r2dbc/postgresql/client/Binding.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.r2dbc.postgresql.message.Format;
import io.r2dbc.postgresql.util.Assert;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

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

private final List<Parameter> parameters;

private final int[] types;

/**
* Create a new instance.
*
Expand All @@ -47,6 +50,7 @@ public final class Binding {
public Binding(int expectedSize) {
this.expectedSize = expectedSize;
this.parameters = new ArrayList<>(Collections.nCopies(expectedSize, UNSPECIFIED));
this.types = new int[expectedSize];
}

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

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

this.parameters.set(index, parameter);
this.types[index] = parameter.getType();

return this;
}
Expand Down Expand Up @@ -97,8 +101,15 @@ public List<Format> getParameterFormats() {
*
* @return the types of the parameters in the binding
*/
public List<Integer> getParameterTypes() {
return getTransformedParameters(Parameter::getType);
public int[] getParameterTypes() {

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

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

Flux<Publisher<? extends ByteBuf>> parameterValues() {
return Flux.fromIterable(this.parameters).map(Parameter::getValue);
}

@Override
public int hashCode() {
return Objects.hash(this.parameters);
Expand All @@ -119,6 +134,10 @@ public boolean isEmpty() {
return this.parameters.isEmpty();
}

public int size() {
return this.parameters.size();
}

@Override
public String toString() {
return "Binding{" +
Expand All @@ -140,14 +159,28 @@ public void validate() {
}

private <T> List<T> getTransformedParameters(Function<Parameter, T> transformer) {
List<T> transformed = new ArrayList<>(this.parameters.size());

if (this.parameters.isEmpty()) {
return Collections.emptyList();
}

List<T> transformed = null;

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

if (transformed == null) {
if (this.parameters.size() == 1) {
return Collections.singletonList(transformer.apply(parameter));
}

transformed = new ArrayList<>(this.parameters.size());
}


transformed.add(transformer.apply(parameter));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ void constructorNoClient() {
void getName() {
// @formatter:off
Client client = TestClient.builder()
.expectRequest(new Parse("S_0", Collections.singletonList(100), "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
.expectRequest(new Parse("S_0", new int[]{100}, "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
.thenRespond(ParseComplete.INSTANCE)
.expectRequest(new Parse("S_1", Collections.singletonList(200), "test-query"), new Describe("S_1", ExecutionType.STATEMENT), Sync.INSTANCE)
.expectRequest(new Parse("S_1", new int[]{200}, "test-query"), new Describe("S_1", ExecutionType.STATEMENT), Sync.INSTANCE)
.thenRespond(ParseComplete.INSTANCE)
.expectRequest(new Parse("S_2", Collections.singletonList(200), "test-query-2"), new Describe("S_2", ExecutionType.STATEMENT), Sync.INSTANCE)
.expectRequest(new Parse("S_2", new int[]{200}, "test-query-2"), new Describe("S_2", ExecutionType.STATEMENT), Sync.INSTANCE)
.thenRespond(ParseComplete.INSTANCE)
.build();
// @formatter:on
Expand Down Expand Up @@ -86,7 +86,7 @@ void getName() {
void getNameErrorResponse() {
// @formatter:off
Client client = TestClient.builder()
.expectRequest(new Parse("S_0", Collections.singletonList(100), "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
.expectRequest(new Parse("S_0", new int[]{100}, "test-query"), new Describe("S_0", ExecutionType.STATEMENT), Sync.INSTANCE)
.thenRespond(new ErrorResponse(Collections.emptyList()))
.build();
// @formatter:on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,6 @@

final class PostgresqlColumnMetadataTest {

@Test
void constructorNoName() {
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlColumnMetadata(null, null))
.withMessage("codecs must not be null");
}

@Test
void constructorNoNativeType() {
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlColumnMetadata(MockCodecs.empty(), null))
.withMessage("field must not be null");
}

@Test
void toColumnMetadata() {
MockCodecs codecs = MockCodecs.builder()
Expand All @@ -52,16 +40,4 @@ void toColumnMetadata() {
assertThat(columnMetadata.getPrecision()).isEqualTo(400);
}

@Test
void toColumnMetadataNoCodecs() {
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlColumnMetadata.toColumnMetadata(null, new Field((short) 100, 200, 300, (short) 400, FORMAT_TEXT, "test-name", 500)))
.withMessage("codecs must not be null");
}

@Test
void toColumnMetadataNoField() {
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlColumnMetadata.toColumnMetadata(MockCodecs.empty(), null))
.withMessage("field must not be null");
}

}
4 changes: 2 additions & 2 deletions src/test/java/io/r2dbc/postgresql/PostgresqlResultTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ final class PostgresqlResultTest {

@Test
void constructorNoCodec() {
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlResult(null, Flux.empty(), ExceptionFactory.INSTANCE))
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlResult.toResult(null, Flux.empty(), ExceptionFactory.INSTANCE))
.withMessage("codecs must not be null");
}

@Test
void constructorNoRowMetadata() {
assertThatIllegalArgumentException().isThrownBy(() -> new PostgresqlResult(MockCodecs.empty(), null, ExceptionFactory.INSTANCE))
assertThatIllegalArgumentException().isThrownBy(() -> PostgresqlResult.toResult(MockCodecs.empty(), null, ExceptionFactory.INSTANCE))
.withMessage("messages must not be null");
}

Expand Down
6 changes: 0 additions & 6 deletions src/test/java/io/r2dbc/postgresql/client/BindingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,6 @@

final class BindingTest {

@Test
void addNoIndex() {
assertThatIllegalArgumentException().isThrownBy(() -> new Binding(1).add(null, new Parameter(FORMAT_TEXT, 100, NULL_VALUE)))
.withMessage("index must not be null");
}

@Test
void addNoParameter() {
assertThatIllegalArgumentException().isThrownBy(() -> new Binding(1).add(1, null))
Expand Down
Loading

0 comments on commit c6e1d56

Please sign in to comment.