From 27ff15319c2e566c44e333b9bd59cf210c130c2f Mon Sep 17 00:00:00 2001 From: Henning Rohlfs Date: Wed, 3 Jan 2018 13:08:48 +0100 Subject: [PATCH] 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. --- .../codec/stomp/StompSubframeDecoder.java | 74 ++++++++++++++----- 1 file changed, 55 insertions(+), 19 deletions(-) diff --git a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java index 3ce55f21cbea..b4d6a1495823 100644 --- a/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java +++ b/codec-stomp/src/main/java/io/netty/handler/codec/stomp/StompSubframeDecoder.java @@ -196,7 +196,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t } private StompCommand readCommand(ByteBuf in) { - String commandStr = readLine(in, maxLineLength); + String commandStr = readLine(in, 16); StompCommand command = null; try { command = StompCommand.valueOf(commandStr); @@ -218,18 +218,11 @@ private StompCommand readCommand(ByteBuf in) { } private State readHeaders(ByteBuf buffer, StompHeaders headers) { + AppendableCharSequence buf = new AppendableCharSequence(128); for (;;) { - String line = readLine(buffer, maxLineLength); - if (!line.isEmpty()) { - String[] split = line.split(":"); - 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)) { + boolean headerRead = readHeader(headers, buf, buffer); + if (!headerRead) { + if (headers.contains(StompHeaders.CONTENT_LENGTH)) { contentLength = getContentLength(headers, 0); if (contentLength == 0) { return State.FINALIZE_FRAME_READ; @@ -266,21 +259,18 @@ private static void skipControlCharacters(ByteBuf buffer) { } } - private static String readLine(ByteBuf buffer, int maxLineLength) { - AppendableCharSequence buf = new AppendableCharSequence(128); + private String readLine(ByteBuf buffer, int initialBufferSize) { + AppendableCharSequence buf = new AppendableCharSequence(initialBufferSize); int lineLength = 0; for (;;) { byte nextByte = buffer.readByte(); if (nextByte == StompConstants.CR) { - nextByte = buffer.readByte(); - if (nextByte == StompConstants.LF) { - return buf.toString(); - } + //do nothing } else if (nextByte == StompConstants.LF) { return buf.toString(); } else { if (lineLength >= maxLineLength) { - throw new TooLongFrameException("An STOMP line is larger than " + maxLineLength + " bytes."); + invalidLineLength(); } lineLength ++; buf.append((char) nextByte); @@ -288,6 +278,52 @@ private static String readLine(ByteBuf buffer, int maxLineLength) { } } + 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() { checkpoint(State.SKIP_CONTROL_CHARACTERS); contentLength = -1;