Reduce memory allocations in StompSubframeDecoder.readHeaders

Motivation:
When decoding stomp frames a lot of unnecessary character arrays are created when parsing headers.
For every header, an array is created to read the line into and then more when splitting the line at the colon.

Modifications:
Parse key and value of a header while reading the line instead of afterwards.
Reuse a single AppendableCharSequence.
Reduce initial size of AppendableCharSequence when reading the command as it is expected to be short.

Result:
Allocations when parsing stomp frames have dropped significantly.
This commit is contained in:
Henning Rohlfs 2018-01-03 13:08:48 +01:00 committed by Norman Maurer
parent 4c1e0f596a
commit 27ff15319c

View File

@ -196,7 +196,7 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
private StompCommand readCommand(ByteBuf in) { private StompCommand readCommand(ByteBuf in) {
String commandStr = readLine(in, maxLineLength); String commandStr = readLine(in, 16);
StompCommand command = null; StompCommand command = null;
try { try {
command = StompCommand.valueOf(commandStr); command = StompCommand.valueOf(commandStr);
@ -218,18 +218,11 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
private State readHeaders(ByteBuf buffer, StompHeaders headers) { private State readHeaders(ByteBuf buffer, StompHeaders headers) {
AppendableCharSequence buf = new AppendableCharSequence(128);
for (;;) { for (;;) {
String line = readLine(buffer, maxLineLength); boolean headerRead = readHeader(headers, buf, buffer);
if (!line.isEmpty()) { if (!headerRead) {
String[] split = line.split(":"); if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
if (split.length == 2) {
headers.add(split[0], split[1]);
} else if (validateHeaders) {
throw new IllegalArgumentException("a header value or name contains a prohibited character ':'" +
", " + line);
}
} else {
if (headers.contains(StompHeaders.CONTENT_LENGTH)) {
contentLength = getContentLength(headers, 0); contentLength = getContentLength(headers, 0);
if (contentLength == 0) { if (contentLength == 0) {
return State.FINALIZE_FRAME_READ; return State.FINALIZE_FRAME_READ;
@ -266,21 +259,18 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
} }
private static String readLine(ByteBuf buffer, int maxLineLength) { private String readLine(ByteBuf buffer, int initialBufferSize) {
AppendableCharSequence buf = new AppendableCharSequence(128); AppendableCharSequence buf = new AppendableCharSequence(initialBufferSize);
int lineLength = 0; int lineLength = 0;
for (;;) { for (;;) {
byte nextByte = buffer.readByte(); byte nextByte = buffer.readByte();
if (nextByte == StompConstants.CR) { if (nextByte == StompConstants.CR) {
nextByte = buffer.readByte(); //do nothing
if (nextByte == StompConstants.LF) {
return buf.toString();
}
} else if (nextByte == StompConstants.LF) { } else if (nextByte == StompConstants.LF) {
return buf.toString(); return buf.toString();
} else { } else {
if (lineLength >= maxLineLength) { if (lineLength >= maxLineLength) {
throw new TooLongFrameException("An STOMP line is larger than " + maxLineLength + " bytes."); invalidLineLength();
} }
lineLength ++; lineLength ++;
buf.append((char) nextByte); buf.append((char) nextByte);
@ -288,6 +278,52 @@ public class StompSubframeDecoder extends ReplayingDecoder<State> {
} }
} }
private boolean readHeader(StompHeaders headers, AppendableCharSequence buf, ByteBuf buffer) {
buf.reset();
int lineLength = 0;
String key = null;
boolean valid = false;
for (;;) {
byte nextByte = buffer.readByte();
if (nextByte == StompConstants.COLON && key == null) {
key = buf.toString();
valid = true;
buf.reset();
} else if (nextByte == StompConstants.CR) {
//do nothing
} else if (nextByte == StompConstants.LF) {
if (key == null && lineLength == 0) {
return false;
} else if (valid) {
headers.add(key, buf.toString());
} else if (validateHeaders) {
invalidHeader(key, buf.toString());
}
return true;
} else {
if (lineLength >= maxLineLength) {
invalidLineLength();
}
if (nextByte == StompConstants.COLON && key != null) {
valid = false;
}
lineLength ++;
buf.append((char) nextByte);
}
}
}
private void invalidHeader(String key, String value) {
String line = key != null ? key + ":" + value : value;
throw new IllegalArgumentException("a header value or name contains a prohibited character ':'"
+ ", " + line);
}
private void invalidLineLength() {
throw new TooLongFrameException("An STOMP line is larger than " + maxLineLength + " bytes.");
}
private void resetDecoder() { private void resetDecoder() {
checkpoint(State.SKIP_CONTROL_CHARACTERS); checkpoint(State.SKIP_CONTROL_CHARACTERS);
contentLength = -1; contentLength = -1;