Skip to content

Commit 0ab7f75

Browse files
authored
Performance improvement for sigv4 signing. (#4867)
1. When trimming and removing consecutive spaces during sigv4 normalization, copy word-by-word instead of character-by-character. This reduces the overhead of range and encoding checks in string builder. 2. Increase starting string builder size for canonical headers, to limit resizing (2048 worked well for DynamoDB's get-item). 3. Use a switch statement for whitespace checks instead of consecutive if statements. On my compiler, the switch statement compiles to a jump table which runs quicker.
1 parent 093501d commit 0ab7f75

File tree

1 file changed

+45
-25
lines changed

1 file changed

+45
-25
lines changed

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

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,10 @@ public static List<Pair<String, List<String>>> getCanonicalHeaders(Map<String, L
181181
* Each header-value pair is separated by a newline.
182182
*/
183183
public static String getCanonicalHeadersString(List<Pair<String, List<String>>> canonicalHeaders) {
184-
StringBuilder result = new StringBuilder(512);
184+
// 2048 chosen experimentally to avoid always needing to resize the string builder's internal byte array.
185+
// The minimal DynamoDB get-item request at the time of testing used ~1100 bytes. 2048 was chosen as the
186+
// next-highest power-of-two.
187+
StringBuilder result = new StringBuilder(2048);
185188
canonicalHeaders.forEach(header -> {
186189
result.append(header.left());
187190
result.append(":");
@@ -246,35 +249,42 @@ private static String getCanonicalRequestString(String httpMethod, String canoni
246249
* Matcher object as well.
247250
*/
248251
private static void addAndTrim(StringBuilder result, String value) {
249-
int lengthBefore = result.length();
250-
boolean isStart = true;
251-
boolean previousIsWhiteSpace = false;
252-
253-
for (int i = 0; i < value.length(); i++) {
254-
char ch = value.charAt(i);
255-
if (isWhiteSpace(ch)) {
256-
if (previousIsWhiteSpace || isStart) {
257-
continue;
258-
}
259-
result.append(' ');
260-
previousIsWhiteSpace = true;
261-
} else {
262-
result.append(ch);
263-
isStart = false;
264-
previousIsWhiteSpace = false;
252+
int start = 0;
253+
int valueLength = value.length();
254+
255+
// Find first non-whitespace
256+
while (isWhiteSpace(value.charAt(start))) {
257+
++start;
258+
if (start > valueLength) {
259+
return;
265260
}
266261
}
267262

268-
if (lengthBefore == result.length()) {
269-
return;
263+
// Add things word-by-word
264+
int lastWordStart = start;
265+
boolean lastWasWhitespace = false;
266+
for (int i = start; i < valueLength; i++) {
267+
char c = value.charAt(i);
268+
269+
if (isWhiteSpace(c)) {
270+
if (!lastWasWhitespace) {
271+
// End of word, add word
272+
result.append(value, lastWordStart, i);
273+
lastWasWhitespace = true;
274+
}
275+
} else {
276+
if (lastWasWhitespace) {
277+
// Start of new word, add space
278+
result.append(' ');
279+
lastWordStart = i;
280+
lastWasWhitespace = false;
281+
}
282+
}
270283
}
271284

272-
int lastNonWhitespaceChar = result.length() - 1;
273-
while (isWhiteSpace(result.charAt(lastNonWhitespaceChar))) {
274-
--lastNonWhitespaceChar;
285+
if (!lastWasWhitespace) {
286+
result.append(value, lastWordStart, valueLength);
275287
}
276-
277-
result.setLength(lastNonWhitespaceChar + 1);
278288
}
279289

280290
/**
@@ -365,7 +375,17 @@ private static String getCanonicalQueryString(SortedMap<String, List<String>> ca
365375
}
366376

367377
private static boolean isWhiteSpace(char ch) {
368-
return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\u000b' || ch == '\r' || ch == '\f';
378+
switch (ch) {
379+
case ' ':
380+
case '\t':
381+
case '\n':
382+
case '\u000b':
383+
case '\r':
384+
case '\f':
385+
return true;
386+
default:
387+
return false;
388+
}
369389
}
370390

371391
/**

0 commit comments

Comments
 (0)