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:
parent
083dbe5c59
commit
a7713069a1
@ -666,9 +666,9 @@ public abstract class HttpObjectDecoder extends ByteToMessageDecoder {
|
||||
cEnd = findEndOfString(sb);
|
||||
|
||||
return new String[] {
|
||||
sb.substring(aStart, aEnd),
|
||||
sb.substring(bStart, bEnd),
|
||||
cStart < cEnd? sb.substring(cStart, cEnd) : "" };
|
||||
sb.subStringUnsafe(aStart, aEnd),
|
||||
sb.subStringUnsafe(bStart, bEnd),
|
||||
cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
|
||||
}
|
||||
|
||||
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);
|
||||
if (valueStart == length) {
|
||||
value = EMPTY_VALUE;
|
||||
} else {
|
||||
valueEnd = findEndOfString(sb);
|
||||
value = sb.substring(valueStart, valueEnd);
|
||||
value = sb.subStringUnsafe(valueStart, valueEnd);
|
||||
}
|
||||
}
|
||||
|
||||
private static int findNonWhitespace(CharSequence sb, int offset) {
|
||||
int result;
|
||||
for (result = offset; result < sb.length(); result ++) {
|
||||
if (!Character.isWhitespace(sb.charAt(result))) {
|
||||
break;
|
||||
private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
|
||||
for (int result = offset; result < sb.length(); ++result) {
|
||||
if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return sb.length();
|
||||
}
|
||||
|
||||
private static int findWhitespace(CharSequence sb, int offset) {
|
||||
int result;
|
||||
for (result = offset; result < sb.length(); result ++) {
|
||||
if (Character.isWhitespace(sb.charAt(result))) {
|
||||
break;
|
||||
private static int findWhitespace(AppendableCharSequence sb, int offset) {
|
||||
for (int result = offset; result < sb.length(); ++result) {
|
||||
if (Character.isWhitespace(sb.charAtUnsafe(result))) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return sb.length();
|
||||
}
|
||||
|
||||
private static int findEndOfString(CharSequence sb) {
|
||||
int result;
|
||||
for (result = sb.length(); result > 0; result --) {
|
||||
if (!Character.isWhitespace(sb.charAt(result - 1))) {
|
||||
break;
|
||||
private static int findEndOfString(AppendableCharSequence sb) {
|
||||
for (int result = sb.length() - 1; result > 0; --result) {
|
||||
if (!Character.isWhitespace(sb.charAtUnsafe(result))) {
|
||||
return result + 1;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static class HeaderParser implements ByteProcessor {
|
||||
|
@ -30,6 +30,9 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
|
||||
}
|
||||
|
||||
private AppendableCharSequence(char[] chars) {
|
||||
if (chars.length < 1) {
|
||||
throw new IllegalArgumentException("length: " + chars.length + " (length: >= 1)");
|
||||
}
|
||||
this.chars = chars;
|
||||
pos = chars.length;
|
||||
}
|
||||
@ -47,6 +50,17 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
|
||||
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
|
||||
public AppendableCharSequence subSequence(int start, int end) {
|
||||
return new AppendableCharSequence(Arrays.copyOfRange(chars, start, end));
|
||||
@ -54,17 +68,12 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
|
||||
|
||||
@Override
|
||||
public AppendableCharSequence append(char c) {
|
||||
if (pos == chars.length) {
|
||||
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);
|
||||
try {
|
||||
chars[pos++] = c;
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
expand();
|
||||
chars[pos - 1] = c;
|
||||
}
|
||||
chars[pos++] = c;
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -121,6 +130,26 @@ public final class AppendableCharSequence implements CharSequence, Appendable {
|
||||
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) {
|
||||
int newCapacity = array.length;
|
||||
do {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user