HttpObjectDecoder performance improvements

Motivation:
The HttpObjectDecoder is on the hot code path for the http codec. There are a few hot methods which can be modified to improve performance.

Modifications:
- Modify AppendableCharSequence to provide unsafe methods which don't need to re-check bounds for every call.
- Update HttpObjectDecoder methods to take advantage of new AppendableCharSequence methods.

Result:
Peformance boost for decoding http objects.
This commit is contained in:
Scott Mitchell 2015-07-25 08:03:45 -07:00
parent dbc078ccf4
commit 9d347ffb91
3 changed files with 146 additions and 33 deletions

View File

@ -666,9 +666,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
cEnd = findEndOfString(sb); cEnd = findEndOfString(sb);
return new String[] { return new String[] {
sb.substring(aStart, aEnd), sb.subStringUnsafe(aStart, aEnd),
sb.substring(bStart, bEnd), sb.subStringUnsafe(bStart, bEnd),
cStart < cEnd? sb.substring(cStart, cEnd) : "" }; cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
} }
private void splitHeader(AppendableCharSequence sb) { private void splitHeader(AppendableCharSequence sb) {
@ -694,44 +694,41 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
} }
} }
name = sb.substring(nameStart, nameEnd); name = sb.subStringUnsafe(nameStart, nameEnd);
valueStart = findNonWhitespace(sb, colonEnd); valueStart = findNonWhitespace(sb, colonEnd);
if (valueStart == length) { if (valueStart == length) {
value = EMPTY_VALUE; value = EMPTY_VALUE;
} else { } else {
valueEnd = findEndOfString(sb); valueEnd = findEndOfString(sb);
value = sb.substring(valueStart, valueEnd); value = sb.subStringUnsafe(valueStart, valueEnd);
} }
} }
private static int findNonWhitespace(CharSequence sb, int offset) { private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
int result; for (int result = offset; result < sb.length(); ++result) {
for (result = offset; result < sb.length(); result ++) { if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
if (!Character.isWhitespace(sb.charAt(result))) { return result;
break;
} }
} }
return result; return sb.length();
} }
private static int findWhitespace(CharSequence sb, int offset) { private static int findWhitespace(AppendableCharSequence sb, int offset) {
int result; for (int result = offset; result < sb.length(); ++result) {
for (result = offset; result < sb.length(); result ++) { if (Character.isWhitespace(sb.charAtUnsafe(result))) {
if (Character.isWhitespace(sb.charAt(result))) { return result;
break;
} }
} }
return result; return sb.length();
} }
private static int findEndOfString(CharSequence sb) { private static int findEndOfString(AppendableCharSequence sb) {
int result; for (int result = sb.length() - 1; result > 0; --result) {
for (result = sb.length(); result > 0; result --) { if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
if (!Character.isWhitespace(sb.charAt(result - 1))) { return result + 1;
break;
} }
} }
return result; return 0;
} }
private static class HeaderParser implements ByteProcessor { private static class HeaderParser implements ByteProcessor {

View File

@ -30,6 +30,9 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
} }
private AppendableCharSequence(char[] chars) { private AppendableCharSequence(char[] chars) {
if (chars.length < 1) {
throw new IllegalArgumentException("length: " + chars.length + " (length: >= 1)");
}
this.chars = chars; this.chars = chars;
pos = chars.length; pos = chars.length;
} }
@ -47,6 +50,17 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
return chars[index]; return chars[index];
} }
/**
* Access a value in this {@link CharSequence}.
* This method is considered unsafe as index values are assumed to be legitimate.
* Only underlying array bounds checking is done.
* @param index The index to access the underlying array at.
* @return The value at {@code index}.
*/
public char charAtUnsafe(int index) {
return chars[index];
}
@Override @Override
public AppendableCharSequence subSequence(int start, int end) { public AppendableCharSequence subSequence(int start, int end) {
return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end)); return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end));
@ -54,17 +68,12 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
@Override @Override
public AppendableCharSequence append(char c) { public AppendableCharSequence append(char c) {
if (pos == chars.length) { try {
char[] old = chars; chars[pos++] = c;
// double it } catch (IndexOutOfBoundsException e) {
int len = old.length << 1; expand();
if (len < 0) { chars[pos - 1] = c;
throw new IllegalStateException();
}
chars = new char[len];
System.arraycopy(old, 0, chars, 0, old.length);
} }
chars[pos++] = c;
return this; return this;
} }
@ -121,6 +130,26 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
return new String(chars, start, length); return new String(chars, start, length);
} }
/**
* Create a new {@link String} from the given start to end.
* This method is considered unsafe as index values are assumed to be legitimate.
* Only underlying array bounds checking is done.
*/
public String subStringUnsafe(int start, int end) {
return new String(chars, start, end - start);
}
private void expand() {
char[] old = chars;
// double it
int len = old.length << 1;
if (len < 0) {
throw new IllegalStateException();
}
chars = new char[len];
System.arraycopy(old, 0, chars, 0, old.length);
}
private static char[] expand(char[] array, int neededSpace, int size) { private static char[] expand(char[] array, int neededSpace, int size) {
int newCapacity = array.length; int newCapacity = array.length;
do { do {

View File

@ -0,0 +1,87 @@
/*
* Copyright 2015 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package io.netty.microbenchmark.common;
import java.util.Random;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import io.netty.microbench.util.AbstractMicrobenchmark;
@Threads(1)
@Warmup(iterations = 5)
@Measurement(iterations = 5)
public class AppendableCharSequenceBenchmark extends AbstractMicrobenchmark {
@Param({ "32", "64", "128", "256" })
private int charsInitSize;
@Param({ "10", "100", "10000", "1000000" })
private int simulatedDataSize;
private static final Random rand = new Random();
private char[] chars;
private char simulatedData;
private int pos;
@Setup(Level.Trial)
public void setup() {
chars = new char[charsInitSize];
simulatedData = (char) rand.nextInt();
}
@Benchmark
public void appendCheckBeforeCopy() {
checkReset();
if (pos == chars.length) {
expand();
}
chars[pos++] = simulatedData;
}
@Benchmark
public void appendCatchExceptionAfter() {
checkReset();
try {
chars[pos++] = simulatedData;
} catch (IndexOutOfBoundsException e) {
expand();
chars[pos - 1] = simulatedData;
}
}
private void checkReset() {
if (pos == simulatedDataSize) {
pos = 0;
chars = new char[charsInitSize];
}
}
private void expand() {
char[] old = chars;
// double it
int len = old.length << 1;
if (len < 0) {
throw new IllegalStateException();
}
chars = new char[len];
System.arraycopy(old, 0, chars, 0, old.length);
}
}