Improve performance of Base64.decode and encode methods.
Motivation: The decode and encode method uses getByte(...) and setByte(...) in loops which can be very expensive because of bounds / reference-count checking. Beside this it also slows-down a lot when paranoid leak-detection is enabled as it will track each access. Modifications: - Pack bytes into int / short and so reduce operations on the ByteBuf - Use ByteProcessor to reduce getByte calls. Result: Better performance in general. Also when you run the build with -Pleak the handler module will build in 1/4 of the time it took before.
This commit is contained in:
parent
0623c6c533
commit
7feb92959e
@ -21,6 +21,11 @@ package io.netty.handler.codec.base64;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.util.ByteProcessor;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Utility class for {@link ByteBuf} that encodes and decodes to and from
|
||||
@ -109,7 +114,6 @@ public final class Base64 {
|
||||
|
||||
public static ByteBuf encode(
|
||||
ByteBuf src, int off, int len, boolean breakLines, Base64Dialect dialect, ByteBufAllocator allocator) {
|
||||
|
||||
if (src == null) {
|
||||
throw new NullPointerException("src");
|
||||
}
|
||||
@ -117,17 +121,18 @@ public final class Base64 {
|
||||
throw new NullPointerException("dialect");
|
||||
}
|
||||
|
||||
int len43 = len * 4 / 3;
|
||||
int len43 = (len << 2) / 3;
|
||||
ByteBuf dest = allocator.buffer(
|
||||
len43 +
|
||||
(len % 3 > 0 ? 4 : 0) + // Account for padding
|
||||
(breakLines ? len43 / MAX_LINE_LENGTH : 0)).order(src.order()); // New lines
|
||||
byte[] alphabet = alphabet(dialect);
|
||||
int d = 0;
|
||||
int e = 0;
|
||||
int len2 = len - 2;
|
||||
int lineLength = 0;
|
||||
for (; d < len2; d += 3, e += 4) {
|
||||
encode3to4(src, d + off, 3, dest, e, dialect);
|
||||
encode3to4(src, d + off, 3, dest, e, alphabet);
|
||||
|
||||
lineLength += 4;
|
||||
|
||||
@ -139,7 +144,7 @@ public final class Base64 {
|
||||
} // end for: each piece of array
|
||||
|
||||
if (d < len) {
|
||||
encode3to4(src, d + off, len - d, dest, e, dialect);
|
||||
encode3to4(src, d + off, len - d, dest, e, alphabet);
|
||||
e += 4;
|
||||
} // end if: some padding needed
|
||||
|
||||
@ -152,11 +157,7 @@ public final class Base64 {
|
||||
}
|
||||
|
||||
private static void encode3to4(
|
||||
ByteBuf src, int srcOffset, int numSigBytes,
|
||||
ByteBuf dest, int destOffset, Base64Dialect dialect) {
|
||||
|
||||
byte[] ALPHABET = alphabet(dialect);
|
||||
|
||||
ByteBuf src, int srcOffset, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
|
||||
// 1 2 3
|
||||
// 01234567890123456789012345678901 Bit position
|
||||
// --------000000001111111122222222 Array position from threeBytes
|
||||
@ -168,29 +169,109 @@ public final class Base64 {
|
||||
// significant bytes passed in the array.
|
||||
// We have to shift left 24 in order to flush out the 1's that appear
|
||||
// when Java treats a value as negative that is cast from a byte to an int.
|
||||
int inBuff =
|
||||
(numSigBytes > 0? src.getByte(srcOffset) << 24 >>> 8 : 0) |
|
||||
(numSigBytes > 1? src.getByte(srcOffset + 1) << 24 >>> 16 : 0) |
|
||||
(numSigBytes > 2? src.getByte(srcOffset + 2) << 24 >>> 24 : 0);
|
||||
|
||||
if (src.order() == ByteOrder.BIG_ENDIAN) {
|
||||
final int inBuff;
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
||||
dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]);
|
||||
dest.setByte(destOffset + 3, ALPHABET[inBuff & 0x3f]);
|
||||
case 1:
|
||||
inBuff = toInt(src.getByte(srcOffset));
|
||||
break;
|
||||
case 2:
|
||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
||||
dest.setByte(destOffset + 2, ALPHABET[inBuff >>> 6 & 0x3f]);
|
||||
dest.setByte(destOffset + 3, EQUALS_SIGN);
|
||||
inBuff = toIntBE(src.getShort(srcOffset));
|
||||
break;
|
||||
default:
|
||||
inBuff = numSigBytes <= 0 ? 0 : toIntBE(src.getMedium(srcOffset));
|
||||
break;
|
||||
}
|
||||
encode3to4BigEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
|
||||
} else {
|
||||
final int inBuff;
|
||||
switch (numSigBytes) {
|
||||
case 1:
|
||||
inBuff = toInt(src.getByte(srcOffset));
|
||||
break;
|
||||
case 2:
|
||||
inBuff = toIntLE(src.getShort(srcOffset));
|
||||
break;
|
||||
default:
|
||||
inBuff = numSigBytes <= 0 ? 0 : toIntLE(src.getMedium(srcOffset));
|
||||
break;
|
||||
}
|
||||
encode3to4LittleEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
|
||||
}
|
||||
}
|
||||
|
||||
private static int toInt(byte value) {
|
||||
return (value & 0xff) << 16;
|
||||
}
|
||||
|
||||
private static int toIntBE(short value) {
|
||||
return (value & 0xff00) << 8 | (value & 0xff) << 8;
|
||||
}
|
||||
|
||||
private static int toIntLE(short value) {
|
||||
return (value & 0xff) << 16 | (value & 0xff00);
|
||||
}
|
||||
|
||||
private static int toIntBE(int mediumValue) {
|
||||
return (mediumValue & 0xff0000) | (mediumValue & 0xff00) | (mediumValue & 0xff);
|
||||
}
|
||||
|
||||
private static int toIntLE(int mediumValue) {
|
||||
return (mediumValue & 0xff) << 16 | (mediumValue & 0xff00) | (mediumValue & 0xff0000) >>> 16;
|
||||
}
|
||||
|
||||
private static void encode3to4BigEndian(
|
||||
int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
|
||||
// Packing bytes into an int to reduce bound and reference count checking.
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||
alphabet[inBuff >>> 6 & 0x3f] << 8 |
|
||||
alphabet[inBuff & 0x3f]);
|
||||
break;
|
||||
case 2:
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||
alphabet[inBuff >>> 6 & 0x3f] << 8 |
|
||||
EQUALS_SIGN);
|
||||
break;
|
||||
case 1:
|
||||
dest.setByte(destOffset , ALPHABET[inBuff >>> 18 ]);
|
||||
dest.setByte(destOffset + 1, ALPHABET[inBuff >>> 12 & 0x3f]);
|
||||
dest.setByte(destOffset + 2, EQUALS_SIGN);
|
||||
dest.setByte(destOffset + 3, EQUALS_SIGN);
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 16 |
|
||||
EQUALS_SIGN << 8 |
|
||||
EQUALS_SIGN);
|
||||
break;
|
||||
default:
|
||||
// NOOP
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void encode3to4LittleEndian(
|
||||
int inBuff, int numSigBytes, ByteBuf dest, int destOffset, byte[] alphabet) {
|
||||
// Packing bytes into an int to reduce bound and reference count checking.
|
||||
switch (numSigBytes) {
|
||||
case 3:
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 8 |
|
||||
alphabet[inBuff >>> 6 & 0x3f] << 16 |
|
||||
alphabet[inBuff & 0x3f] << 24);
|
||||
break;
|
||||
case 2:
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 8 |
|
||||
alphabet[inBuff >>> 6 & 0x3f] << 16 |
|
||||
EQUALS_SIGN << 24);
|
||||
break;
|
||||
case 1:
|
||||
dest.setInt(destOffset, alphabet[inBuff >>> 18 ] |
|
||||
alphabet[inBuff >>> 12 & 0x3f] << 8 |
|
||||
EQUALS_SIGN << 16 |
|
||||
EQUALS_SIGN << 24);
|
||||
break;
|
||||
default:
|
||||
// NOOP
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -200,7 +281,6 @@ public final class Base64 {
|
||||
}
|
||||
|
||||
public static ByteBuf decode(ByteBuf src, Base64Dialect dialect) {
|
||||
|
||||
if (src == null) {
|
||||
throw new NullPointerException("src");
|
||||
}
|
||||
@ -222,7 +302,6 @@ public final class Base64 {
|
||||
|
||||
public static ByteBuf decode(
|
||||
ByteBuf src, int off, int len, Base64Dialect dialect, ByteBufAllocator allocator) {
|
||||
|
||||
if (src == null) {
|
||||
throw new NullPointerException("src");
|
||||
}
|
||||
@ -230,85 +309,96 @@ public final class Base64 {
|
||||
throw new NullPointerException("dialect");
|
||||
}
|
||||
|
||||
byte[] DECODABET = decodabet(dialect);
|
||||
// Using a ByteProcessor to reduce bound and reference count checking.
|
||||
return new Decoder().decode(src, off, len, allocator, dialect);
|
||||
}
|
||||
|
||||
int len34 = len * 3 / 4;
|
||||
ByteBuf dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output
|
||||
int outBuffPosn = 0;
|
||||
private static final class Decoder implements ByteProcessor {
|
||||
private final byte[] b4 = new byte[4];
|
||||
private int b4Posn;
|
||||
private byte sbiCrop;
|
||||
private byte sbiDecode;
|
||||
private byte[] decodabet;
|
||||
private int outBuffPosn;
|
||||
private ByteBuf dest;
|
||||
|
||||
byte[] b4 = new byte[4];
|
||||
int b4Posn = 0;
|
||||
int i;
|
||||
byte sbiCrop;
|
||||
byte sbiDecode;
|
||||
for (i = off; i < off + len; i ++) {
|
||||
sbiCrop = (byte) (src.getByte(i) & 0x7f); // Only the low seven bits
|
||||
sbiDecode = DECODABET[sbiCrop];
|
||||
ByteBuf decode(ByteBuf src, int off, int len, ByteBufAllocator allocator, Base64Dialect dialect) {
|
||||
int len34 = (len * 3) >>> 2;
|
||||
dest = allocator.buffer(len34).order(src.order()); // Upper limit on size of output
|
||||
|
||||
decodabet = decodabet(dialect);
|
||||
try {
|
||||
src.forEachByte(off, len, this);
|
||||
return dest.slice(0, outBuffPosn);
|
||||
} catch (Throwable cause) {
|
||||
dest.release();
|
||||
PlatformDependent.throwException(cause);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
sbiCrop = (byte) (value & 0x7f); // Only the low seven bits
|
||||
sbiDecode = decodabet[sbiCrop];
|
||||
|
||||
if (sbiDecode >= WHITE_SPACE_ENC) { // White space, Equals sign or better
|
||||
if (sbiDecode >= EQUALS_SIGN_ENC) { // Equals sign or better
|
||||
b4[b4Posn ++] = sbiCrop;
|
||||
if (b4Posn > 3) { // Quartet built
|
||||
outBuffPosn += decode4to3(
|
||||
b4, 0, dest, outBuffPosn, dialect);
|
||||
outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet);
|
||||
b4Posn = 0;
|
||||
|
||||
// If that was the equals sign, break out of 'for' loop
|
||||
if (sbiCrop == EQUALS_SIGN) {
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"bad Base64 input character at " + i + ": " +
|
||||
src.getUnsignedByte(i) + " (decimal)");
|
||||
}
|
||||
"invalid bad Base64 input character: " + (short) (value & 0xFF) + " (decimal)");
|
||||
}
|
||||
|
||||
return dest.slice(0, outBuffPosn);
|
||||
}
|
||||
|
||||
private static int decode4to3(
|
||||
byte[] src, int srcOffset,
|
||||
ByteBuf dest, int destOffset, Base64Dialect dialect) {
|
||||
|
||||
byte[] DECODABET = decodabet(dialect);
|
||||
|
||||
if (src[srcOffset + 2] == EQUALS_SIGN) {
|
||||
private static int decode4to3(byte[] src, ByteBuf dest, int destOffset, byte[] decodabet) {
|
||||
if (src[2] == EQUALS_SIGN) {
|
||||
// Example: Dk==
|
||||
int outBuff =
|
||||
(DECODABET[src[srcOffset ]] & 0xFF) << 18 |
|
||||
(DECODABET[src[srcOffset + 1]] & 0xFF) << 12;
|
||||
|
||||
dest.setByte(destOffset, (byte) (outBuff >>> 16));
|
||||
dest.setByte(destOffset, (byte) ((decodabet[src[0]] & 0xff) << 2 | (decodabet[src[1]] & 0xff) >>> 4));
|
||||
return 1;
|
||||
} else if (src[srcOffset + 3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff =
|
||||
(DECODABET[src[srcOffset ]] & 0xFF) << 18 |
|
||||
(DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
|
||||
(DECODABET[src[srcOffset + 2]] & 0xFF) << 6;
|
||||
}
|
||||
|
||||
dest.setByte(destOffset , (byte) (outBuff >>> 16));
|
||||
dest.setByte(destOffset + 1, (byte) (outBuff >>> 8));
|
||||
return 2;
|
||||
if (src[3] == EQUALS_SIGN) {
|
||||
// Example: DkL=
|
||||
int outBuff = (decodabet[src[0]] & 0xff) << 18 |
|
||||
(decodabet[src[1]] & 0xff) << 12 |
|
||||
(decodabet[src[2]] & 0xff) << 6;
|
||||
|
||||
// Packing bytes into a short to reduce bound and reference count checking.
|
||||
if (dest.order() == ByteOrder.BIG_ENDIAN) {
|
||||
dest.setShort(destOffset, (short) ((outBuff & 0xff0000) >>> 8 | (outBuff & 0xff00) >>> 8));
|
||||
} else {
|
||||
dest.setShort(destOffset, (short) ((outBuff & 0xff0000) >>> 16 | (outBuff & 0xff00)));
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Example: DkLE
|
||||
int outBuff;
|
||||
final int outBuff;
|
||||
try {
|
||||
outBuff =
|
||||
(DECODABET[src[srcOffset ]] & 0xFF) << 18 |
|
||||
(DECODABET[src[srcOffset + 1]] & 0xFF) << 12 |
|
||||
(DECODABET[src[srcOffset + 2]] & 0xFF) << 6 |
|
||||
DECODABET[src[srcOffset + 3]] & 0xFF;
|
||||
outBuff = (decodabet[src[0]] & 0xff) << 18 |
|
||||
(decodabet[src[1]] & 0xff) << 12 |
|
||||
(decodabet[src[2]] & 0xff) << 6 |
|
||||
decodabet[src[3]] & 0xff;
|
||||
} catch (IndexOutOfBoundsException ignored) {
|
||||
throw new IllegalArgumentException("not encoded in Base64");
|
||||
}
|
||||
|
||||
dest.setByte(destOffset , (byte) (outBuff >> 16));
|
||||
dest.setByte(destOffset + 1, (byte) (outBuff >> 8));
|
||||
dest.setByte(destOffset + 2, (byte) outBuff);
|
||||
// Just directly set it as medium
|
||||
if (dest.order() == ByteOrder.BIG_ENDIAN) {
|
||||
dest.setMedium(destOffset, outBuff);
|
||||
} else {
|
||||
dest.setMedium(destOffset, ByteBufUtil.swapMedium(outBuff));
|
||||
}
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,12 @@ package io.netty.handler.codec.base64;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
@ -116,4 +118,42 @@ public class Base64Test {
|
||||
encoded.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeBE() {
|
||||
testEncodeDecode(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeDecodeLE() {
|
||||
testEncodeDecode(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
private static void testEncodeDecode(ByteOrder order) {
|
||||
testEncodeDecode(64, order);
|
||||
testEncodeDecode(128, order);
|
||||
testEncodeDecode(512, order);
|
||||
testEncodeDecode(1024, order);
|
||||
testEncodeDecode(4096, order);
|
||||
testEncodeDecode(8192, order);
|
||||
testEncodeDecode(16384, order);
|
||||
}
|
||||
|
||||
private static void testEncodeDecode(int size, ByteOrder order) {
|
||||
byte[] bytes = new byte[size];
|
||||
PlatformDependent.threadLocalRandom().nextBytes(bytes);
|
||||
|
||||
ByteBuf src = Unpooled.wrappedBuffer(bytes).order(order);
|
||||
ByteBuf encoded = Base64.encode(src);
|
||||
ByteBuf decoded = Base64.decode(encoded);
|
||||
ByteBuf expectedBuf = Unpooled.wrappedBuffer(bytes);
|
||||
try {
|
||||
assertEquals(expectedBuf, decoded);
|
||||
} finally {
|
||||
src.release();
|
||||
encoded.release();
|
||||
decoded.release();
|
||||
expectedBuf.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user