Skip to content

Commit 1d15408

Browse files
committed
Combine chunking + encoding
Combine these two steps to improve performance by reducing memory allocations.
1 parent a222719 commit 1d15408

File tree

5 files changed

+67
-27
lines changed

5 files changed

+67
-27
lines changed

core/http-auth-aws/src/main/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedPublisher.java

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
*/
6262
@SdkInternalApi
6363
public class ChunkedEncodedPublisher implements Publisher<ByteBuffer> {
64+
private final ByteBuffer EMPTY = ByteBuffer.allocate(0);
6465
private static final byte[] CRLF = {'\r', '\n'};
6566
private static final byte SEMICOLON = ';';
6667
private static final byte EQUALS = '=';
@@ -83,6 +84,7 @@ public ChunkedEncodedPublisher(Builder b) {
8384
this.extensions.addAll(b.extensions);
8485
this.trailers.addAll(b.trailers);
8586
this.addEmptyTrailingChunk = b.addEmptyTrailingChunk;
87+
this.chunkBuffer = ByteBuffer.allocate(chunkSize);
8688
}
8789

8890
@Override
@@ -93,9 +95,8 @@ public void subscribe(Subscriber<? super ByteBuffer> subscriber) {
9395
Publisher<Iterable<ByteBuffer>> chunked = chunk(lengthEnforced);
9496
Publisher<Iterable<ByteBuffer>> trailingAdded = addTrailingChunks(chunked);
9597
Publisher<ByteBuffer> flattened = flatten(trailingAdded);
96-
Publisher<ByteBuffer> encoded = map(flattened, this::encodeChunk);
9798

98-
encoded.subscribe(subscriber);
99+
flattened.subscribe(subscriber);
99100
}
100101

101102
public static Builder builder() {
@@ -105,7 +106,7 @@ public static Builder builder() {
105106
private void resetState() {
106107
extensions.forEach(Resettable::reset);
107108
trailers.forEach(Resettable::reset);
108-
chunkBuffer = null;
109+
chunkBuffer = ByteBuffer.allocate(chunkSize);
109110
}
110111

111112
private Iterable<Iterable<ByteBuffer>> getTrailingChunks() {
@@ -114,12 +115,13 @@ private Iterable<Iterable<ByteBuffer>> getTrailingChunks() {
114115
if (chunkBuffer != null) {
115116
chunkBuffer.flip();
116117
if (chunkBuffer.hasRemaining()) {
117-
trailing.add(chunkBuffer);
118+
trailing.add(encodeChunk(chunkBuffer));
119+
chunkBuffer = null;
118120
}
119121
}
120122

121123
if (addEmptyTrailingChunk) {
122-
trailing.add(ByteBuffer.allocate(0));
124+
trailing.add(encodeChunk(EMPTY.duplicate()));
123125
}
124126

125127
return Collections.singletonList(trailing);
@@ -174,6 +176,7 @@ private ByteBuffer encodeChunk(ByteBuffer byteBuffer) {
174176
.mapToInt(t -> t.remaining() + CRLF.length)
175177
.sum();
176178

179+
177180
int encodedLen = chunkSizeHex.length + extensionsLength + CRLF.length + contentLen + trailerLen + CRLF.length;
178181

179182
if (isTrailerChunk) {
@@ -272,37 +275,57 @@ protected ChunkingSubscriber(Subscriber<? super Iterable<ByteBuffer>> subscriber
272275
}
273276

274277
@Override
275-
public void onNext(ByteBuffer byteBuffer) {
276-
if (chunkBuffer == null) {
277-
chunkBuffer = ByteBuffer.allocate(chunkSize);
278-
}
279-
280-
long totalBufferedBytes = (long) chunkBuffer.position() + byteBuffer.remaining();
278+
public void onNext(ByteBuffer inputBuffer) {
279+
long totalBufferedBytes = (long) chunkBuffer.position() + inputBuffer.remaining();
280+
// compute the number full chunks we have currently
281281
int nBufferedChunks = (int) (totalBufferedBytes / chunkSize);
282282

283283
List<ByteBuffer> chunks = new ArrayList<>(nBufferedChunks);
284284

285285
if (nBufferedChunks > 0) {
286-
for (int i = 0; i < nBufferedChunks; i++) {
287-
ByteBuffer slice = byteBuffer.slice();
288-
int maxBytesToCopy = Math.min(chunkBuffer.remaining(), slice.remaining());
289-
slice.limit(maxBytesToCopy);
286+
// We have some data from the previous inputBuffer
287+
if (chunkBuffer.position() > 0) {
288+
int bytesToFill = chunkBuffer.remaining();
290289

290+
ByteBuffer slice = inputBuffer.slice();
291+
292+
slice.limit(slice.position() + bytesToFill);
293+
inputBuffer.position(inputBuffer.position() + bytesToFill);
294+
295+
// At this point, we know chunkBuffer is full since inputBuffer has at least enough bytes to make up a full
296+
// chunk along with the data already in chunkBuffer
291297
chunkBuffer.put(slice);
292-
if (!chunkBuffer.hasRemaining()) {
293-
chunkBuffer.flip();
294-
chunks.add(chunkBuffer);
295-
chunkBuffer = ByteBuffer.allocate(chunkSize);
296-
}
298+
chunkBuffer.flip();
299+
chunks.add(encodeChunk(chunkBuffer));
300+
301+
chunkBuffer.flip();
297302

298-
byteBuffer.position(byteBuffer.position() + maxBytesToCopy);
303+
nBufferedChunks--;
304+
}
305+
306+
// Now encode all the remaining full chunks from inputBuffer.
307+
// At this point chunkBuffer has no data in it; slice off chunks from inputBuffer and encode directly
308+
for (int i = 0; i < nBufferedChunks; i++) {
309+
ByteBuffer slice = inputBuffer.slice();
310+
311+
int sliceLimit = Math.min(slice.limit(), chunkSize);
312+
slice.limit(sliceLimit);
313+
314+
inputBuffer.position(inputBuffer.position() + slice.remaining());
315+
316+
if (slice.remaining() >= chunkSize) {
317+
slice.limit(slice.position() + chunkSize);
318+
chunks.add(encodeChunk(slice));
319+
} else {
320+
chunkBuffer.put(slice);
321+
}
299322
}
300323

301-
if (byteBuffer.hasRemaining()) {
302-
chunkBuffer.put(byteBuffer);
324+
if (inputBuffer.hasRemaining()) {
325+
chunkBuffer.put(inputBuffer);
303326
}
304327
} else {
305-
chunkBuffer.put(byteBuffer);
328+
chunkBuffer.put(inputBuffer);
306329
}
307330

308331
subscriber.onNext(chunks);

core/http-auth-aws/src/test/java/software/amazon/awssdk/http/auth/aws/internal/signer/chunkedencoding/ChunkedEncodedPublisherTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ void subscribe_randomElementSizes_chunksHaveExtensions_dataChunkedCorrectly() {
264264
.build();
265265

266266
List<ByteBuffer> chunks = getAllElements(chunkedPublisher);
267+
assertThat(chunks.size()).isEqualTo(24);
267268

268269
chunks.forEach(c -> {
269270
String header = StandardCharsets.UTF_8.decode(getHeader(c)).toString();

services/s3/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,9 @@
235235
<artifactId>rxjava</artifactId>
236236
<scope>test</scope>
237237
</dependency>
238+
<dependency>
239+
<groupId>org.apache.logging.log4j</groupId>
240+
<artifactId>log4j-slf4j-impl</artifactId>
241+
</dependency>
238242
</dependencies>
239243
</project>

services/s3/src/it/resources/log4j2.properties

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ appender.console.name = ConsoleAppender
2020
appender.console.layout.type = PatternLayout
2121
appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable
2222

23-
rootLogger.level = info
23+
rootLogger.level = debug
2424
rootLogger.appenderRef.stdout.ref = ConsoleAppender
2525

2626
# Uncomment below to enable more specific logging
@@ -34,5 +34,5 @@ rootLogger.appenderRef.stdout.ref = ConsoleAppender
3434
#logger.apache.name = org.apache.http.wire
3535
#logger.apache.level = debug
3636
#
37-
#logger.netty.name = io.netty.handler.logging
38-
#logger.netty.level = debug
37+
logger.netty.name = io.netty.handler.logging
38+
logger.netty.level = debug

test/sdk-benchmarks/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@
8282
<version>${awsjavasdk.version}</version>
8383
</dependency>
8484

85+
<dependency>
86+
<groupId>software.amazon.awssdk</groupId>
87+
<artifactId>s3</artifactId>
88+
<version>${awsjavasdk.version}</version>
89+
</dependency>
90+
8591
<dependency>
8692
<groupId>com.amazonaws</groupId>
8793
<artifactId>aws-java-sdk-ec2</artifactId>
@@ -94,6 +100,12 @@
94100
<version>${awsjavasdk.version}</version>
95101
</dependency>
96102

103+
<dependency>
104+
<groupId>software.amazon.awssdk</groupId>
105+
<artifactId>s3</artifactId>
106+
<version>${awsjavasdk.version}</version>
107+
</dependency>
108+
97109
<dependency>
98110
<groupId>software.amazon.awssdk</groupId>
99111
<artifactId>aws-query-protocol</artifactId>

0 commit comments

Comments
 (0)