Skip to content

Commit 7f131c9

Browse files
committed
Change A1: Tell ByteArrayOutputStream required capacity
The byte array in `ByteArrayOutputStream` doubles in size every time capacity is exhausted - for an object just over 1GB in size, the buffer will eventually grow to 2GB in size - unnecessarily large, and involving a lot of array copies (`ceil(log_2 (N/32))` copies). https://github.com/openjdk/jdk/blob/218829e0a2a3ae5599b81733df53557966392033/src/java.base/share/classes/java/io/ByteArrayOutputStream.java#L100-L101 If we just tell the Content Length to initialise the `ByteArrayOutputStream` with a byte array of the right size, the array will never get bigger than it needs to be - and it won't have do array-resizing either.
1 parent 0342a87 commit 7f131c9

File tree

4 files changed

+42
-11
lines changed

4 files changed

+42
-11
lines changed

src/main/java/com/madgag/aws/sdk/async/responsebytes/Main.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.google.common.hash.HashCode;
44
import com.google.common.hash.Hashing;
5-
import com.madgag.aws.sdk.async.responsebytes.awssdk.core.async.AsyncResponseTransformerAlternative;
5+
import com.madgag.aws.sdk.async.responsebytes.awssdk.services.s3.AsyncS3ResponseTransformer;
66
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
77
import software.amazon.awssdk.core.ResponseBytes;
88
import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient;
@@ -23,7 +23,7 @@ public static void main(String[] args) throws ExecutionException, InterruptedExc
2323
KnownS3Object knownS3Object = KnownS3Object.BIG;
2424
ResponseBytes<GetObjectResponse> responseBytes = s3Client.getObject(
2525
GetObjectRequest.builder().bucket(knownS3Object.bucket()).key(knownS3Object.key()).build(),
26-
AsyncResponseTransformerAlternative.toBytes()
26+
AsyncS3ResponseTransformer.toBytes()
2727
).get();
2828
System.out.println(responseBytes.response().contentLength());
2929
HashCode hashOfDownloadedData = Hashing.sha256().hashBytes(responseBytes.asByteBuffer());

src/main/java/com/madgag/aws/sdk/async/responsebytes/awssdk/core/async/AsyncResponseTransformerAlternative.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,15 @@
44
import software.amazon.awssdk.core.ResponseBytes;
55
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
66

7+
import java.util.Optional;
8+
import java.util.function.Function;
9+
710
public class AsyncResponseTransformerAlternative {
811
public static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseBytes<ResponseT>> toBytes() {
9-
return new ByteArrayAsyncResponseTransformerAlternative<>();
12+
return new ByteArrayAsyncResponseTransformerAlternative<>(Optional.empty());
13+
}
14+
15+
public static <ResponseT> AsyncResponseTransformer<ResponseT, ResponseBytes<ResponseT>> toBytes(Function<ResponseT, Integer> f) {
16+
return new ByteArrayAsyncResponseTransformerAlternative<>(Optional.of(f));
1017
}
1118
}

src/main/java/com/madgag/aws/sdk/async/responsebytes/awssdk/core/internal/async/ByteArrayAsyncResponseTransformerAlternative.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,6 @@
1515

1616
package com.madgag.aws.sdk.async.responsebytes.awssdk.core.internal.async;
1717

18-
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
19-
20-
import java.io.ByteArrayOutputStream;
21-
import java.nio.ByteBuffer;
22-
import java.util.concurrent.CompletableFuture;
2318
import org.reactivestreams.Subscriber;
2419
import org.reactivestreams.Subscription;
2520
import software.amazon.awssdk.annotations.SdkInternalApi;
@@ -28,6 +23,14 @@
2823
import software.amazon.awssdk.core.async.SdkPublisher;
2924
import software.amazon.awssdk.utils.BinaryUtils;
3025

26+
import java.io.ByteArrayOutputStream;
27+
import java.nio.ByteBuffer;
28+
import java.util.Optional;
29+
import java.util.concurrent.CompletableFuture;
30+
import java.util.function.Function;
31+
32+
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
33+
3134
/**
3235
* Implementation of {@link AsyncResponseTransformer} that dumps content into a byte array and supports further
3336
* conversions into types, like strings.
@@ -41,9 +44,14 @@
4144
public final class ByteArrayAsyncResponseTransformerAlternative<ResponseT> implements
4245
AsyncResponseTransformer<ResponseT, ResponseBytes<ResponseT>> {
4346

47+
private final Optional<Function<ResponseT, Integer>> knownSize;
4448
private volatile CompletableFuture<byte[]> cf;
4549
private volatile ResponseT response;
4650

51+
public ByteArrayAsyncResponseTransformerAlternative(Optional<Function<ResponseT, Integer>> knownSize) {
52+
this.knownSize = knownSize;
53+
}
54+
4755
@Override
4856
public CompletableFuture<ResponseBytes<ResponseT>> prepare() {
4957
cf = new CompletableFuture<>();
@@ -57,7 +65,9 @@ public void onResponse(ResponseT response) {
5765

5866
@Override
5967
public void onStream(SdkPublisher<ByteBuffer> publisher) {
60-
publisher.subscribe(new BaosSubscriber(cf));
68+
ByteArrayOutputStream baos =
69+
knownSize.map(f -> new ByteArrayOutputStream(f.apply(response))).orElse(new ByteArrayOutputStream());
70+
publisher.subscribe(new BaosSubscriber(cf, baos));
6171
}
6272

6373
@Override
@@ -68,12 +78,13 @@ public void exceptionOccurred(Throwable throwable) {
6878
static class BaosSubscriber implements Subscriber<ByteBuffer> {
6979
private final CompletableFuture<byte[]> resultFuture;
7080

71-
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
81+
private ByteArrayOutputStream baos;
7282

7383
private Subscription subscription;
7484

75-
BaosSubscriber(CompletableFuture<byte[]> resultFuture) {
85+
BaosSubscriber(CompletableFuture<byte[]> resultFuture, ByteArrayOutputStream baos) {
7686
this.resultFuture = resultFuture;
87+
this.baos = baos;
7788
}
7889

7990
@Override
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.madgag.aws.sdk.async.responsebytes.awssdk.services.s3;
2+
3+
import com.madgag.aws.sdk.async.responsebytes.awssdk.core.async.AsyncResponseTransformerAlternative;
4+
import software.amazon.awssdk.core.ResponseBytes;
5+
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
6+
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
7+
8+
public class AsyncS3ResponseTransformer {
9+
10+
public static AsyncResponseTransformer<GetObjectResponse, ResponseBytes<GetObjectResponse>> toBytes() {
11+
return AsyncResponseTransformerAlternative.toBytes(r -> r.contentLength().intValue());
12+
}
13+
}

0 commit comments

Comments
 (0)