Avoid CharsRef Allocations in StreamInput (#44488) (#44519)

* Many messages deserialized from a `StreamInput` only contain short strings, some use-cases of instantiating a `StreamInput` don't deserialize any strings
  * Don't allocate `CharsRef` for small strings to save some allocations (especially on the IO threads)
  * Lazily allocate a larger `CharsRef` if needed for larger strings like we did before and have it live as long as the `StreamInput` like before as well
This commit is contained in:
Armin Braun 2019-07-18 08:52:37 +02:00 committed by GitHub
parent 38d2ada84f
commit 6565825a13
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 23 additions and 8 deletions

View File

@ -392,20 +392,35 @@ public abstract class StreamInput extends InputStream {
return null;
}
// we don't use a CharsRefBuilder since we exactly know the size of the character array up front
// Maximum char-count to de-serialize via the thread-local CharsRef buffer
private static final int SMALL_STRING_LIMIT = 1024;
// Thread-local buffer for smaller strings
private static final ThreadLocal<CharsRef> smallSpare = ThreadLocal.withInitial(() -> new CharsRef(SMALL_STRING_LIMIT));
// Larger buffer used for long strings that can't fit into the thread-local buffer
// We don't use a CharsRefBuilder since we exactly know the size of the character array up front
// this prevents calling grow for every character since we don't need this
private final CharsRef spare = new CharsRef();
private CharsRef largeSpare;
public String readString() throws IOException {
// TODO it would be nice to not call readByte() for every character but we don't know how much to read up-front
// we can make the loop much more complicated but that won't buy us much compared to the bounds checks in readByte()
final int charCount = readArraySize();
if (spare.chars.length < charCount) {
// we don't use ArrayUtils.grow since there is no need to copy the array
spare.chars = new char[ArrayUtil.oversize(charCount, Character.BYTES)];
final CharsRef charsRef;
if (charCount > SMALL_STRING_LIMIT) {
if (largeSpare == null) {
largeSpare = new CharsRef(ArrayUtil.oversize(charCount, Character.BYTES));
} else if (largeSpare.chars.length < charCount) {
// we don't use ArrayUtils.grow since there is no need to copy the array
largeSpare.chars = new char[ArrayUtil.oversize(charCount, Character.BYTES)];
}
charsRef = largeSpare;
} else {
charsRef = smallSpare.get();
}
spare.length = charCount;
final char[] buffer = spare.chars;
charsRef.length = charCount;
final char[] buffer = charsRef.chars;
for (int i = 0; i < charCount; i++) {
final int c = readByte() & 0xff;
switch (c >> 4) {
@ -430,7 +445,7 @@ public abstract class StreamInput extends InputStream {
throw new IOException("Invalid string; unexpected character: " + c + " hex: " + Integer.toHexString(c));
}
}
return spare.toString();
return charsRef.toString();
}
public SecureString readSecureString() throws IOException {