13
0
geforkt von Mirrors/Velocity

Optimize native handling further.

We now try to work within the boundaries given by the native. In the
case of Java natives, we work with byte arrays. With natives, always use
direct buffers.

However, the numbers do favor the natives, since they work with direct
byte buffers, without any copying. For the most part, this commit is
intended to improve the lives of Velocity users on Windows.
Dieser Commit ist enthalten in:
Andrew Steinborn 2018-12-30 06:16:04 -05:00
Ursprung 095a478440
Commit 8588e7f1fe
10 geänderte Dateien mit 121 neuen und 57 gelöschten Zeilen

Datei anzeigen

@ -85,7 +85,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e
jint sourceLength, jint sourceLength,
jlong destinationAddress, jlong destinationAddress,
jint destinationLength, jint destinationLength,
jboolean flush) jboolean finish)
{ {
z_stream* stream = (z_stream*) ctx; z_stream* stream = (z_stream*) ctx;
stream->next_in = (Bytef *) sourceAddress; stream->next_in = (Bytef *) sourceAddress;
@ -93,7 +93,7 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e
stream->avail_in = sourceLength; stream->avail_in = sourceLength;
stream->avail_out = destinationLength; stream->avail_out = destinationLength;
int res = deflate(stream, flush ? Z_FINISH : Z_NO_FLUSH); int res = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH);
switch (res) { switch (res) {
case Z_STREAM_END: case Z_STREAM_END:
// The stream has ended. // The stream has ended.

Datei anzeigen

@ -27,9 +27,14 @@ public class JavaVelocityCompressor implements VelocityCompressor {
public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException { public void inflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()]; if (source.hasArray()) {
source.readBytes(inData); inflater.setInput(source.array(), source.arrayOffset(), source.readableBytes());
inflater.setInput(inData); } else {
byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData);
inflater.setInput(inData);
}
while (!inflater.finished()) { while (!inflater.finished()) {
int read = inflater.inflate(buf); int read = inflater.inflate(buf);
destination.writeBytes(buf, 0, read); destination.writeBytes(buf, 0, read);
@ -41,9 +46,13 @@ public class JavaVelocityCompressor implements VelocityCompressor {
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException { public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
ensureNotDisposed(); ensureNotDisposed();
byte[] inData = new byte[source.readableBytes()]; if (source.hasArray()) {
source.readBytes(inData); deflater.setInput(source.array(), source.arrayOffset(), source.readableBytes());
deflater.setInput(inData); } else {
byte[] inData = new byte[source.readableBytes()];
source.readBytes(inData);
deflater.setInput(inData);
}
deflater.finish(); deflater.finish();
while (!deflater.finished()) { while (!deflater.finished()) {
int bytes = deflater.deflate(buf); int bytes = deflater.deflate(buf);

Datei anzeigen

@ -13,8 +13,7 @@ class NativeZlibDeflate {
native long free(long ctx); native long free(long ctx);
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress, native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
int destinationLength, int destinationLength, boolean finish);
boolean flush);
native void reset(long ctx); native void reset(long ctx);

Datei anzeigen

@ -2,6 +2,7 @@ package com.velocitypowered.natives.encryption;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -22,7 +23,7 @@ public class JavaVelocityCipher implements VelocityCipher {
return new JavaVelocityCipher(false, key); return new JavaVelocityCipher(false, key);
} }
}; };
private static final int INITIAL_BUFFER_SIZE = 1024 * 16; private static final int INITIAL_BUFFER_SIZE = 1024 * 8;
private static final ThreadLocal<byte[]> inBufLocal = ThreadLocal.withInitial( private static final ThreadLocal<byte[]> inBufLocal = ThreadLocal.withInitial(
() -> new byte[INITIAL_BUFFER_SIZE]); () -> new byte[INITIAL_BUFFER_SIZE]);
@ -40,12 +41,19 @@ public class JavaVelocityCipher implements VelocityCipher {
ensureNotDisposed(); ensureNotDisposed();
int inBytes = source.readableBytes(); int inBytes = source.readableBytes();
byte[] inBuf = slurp(source); ByteBuf asHeapBuf = asHeapBuf(source);
int outputSize = cipher.getOutputSize(inBytes); int outputSize = cipher.getOutputSize(inBytes);
byte[] outBuf = new byte[outputSize]; if (!destination.hasArray()) {
cipher.update(inBuf, 0, inBytes, outBuf); byte[] outBuf = new byte[outputSize];
destination.writeBytes(outBuf); cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes, outBuf);
destination.writeBytes(outBuf);
} else {
// If the destination we write to is an array, we can use the backing array directly.
destination.ensureWritable(outputSize);
destination.writerIndex(cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes,
destination.array(), destination.arrayOffset()));
}
} }
@Override @Override
@ -53,14 +61,20 @@ public class JavaVelocityCipher implements VelocityCipher {
ensureNotDisposed(); ensureNotDisposed();
int inBytes = source.readableBytes(); int inBytes = source.readableBytes();
byte[] inBuf = slurp(source); ByteBuf asHeapBuf = asHeapBuf(source);
ByteBuf out = ctx.alloc().heapBuffer(cipher.getOutputSize(inBytes)); ByteBuf out = ctx.alloc().heapBuffer(cipher.getOutputSize(inBytes));
out.writerIndex(cipher.update(inBuf, 0, inBytes, out.array(), out.arrayOffset())); out.writerIndex(cipher.update(asHeapBuf.array(), asHeapBuf.arrayOffset(), inBytes, out.array(),
out.arrayOffset()));
return out; return out;
} }
private static byte[] slurp(ByteBuf source) { private static ByteBuf asHeapBuf(ByteBuf source) {
if (source.hasArray()) {
// If this byte buffer is backed by an array, we can just use this buffer directly.
return source;
}
int inBytes = source.readableBytes(); int inBytes = source.readableBytes();
byte[] inBuf = inBufLocal.get(); byte[] inBuf = inBufLocal.get();
if (inBuf.length <= inBytes) { if (inBuf.length <= inBytes) {
@ -68,7 +82,7 @@ public class JavaVelocityCipher implements VelocityCipher {
inBufLocal.set(inBuf); inBufLocal.set(inBuf);
} }
source.readBytes(inBuf, 0, inBytes); source.readBytes(inBuf, 0, inBytes);
return inBuf; return Unpooled.wrappedBuffer(inBuf, 0, inBytes);
} }
@Override @Override

Datei anzeigen

@ -30,4 +30,31 @@ public class MoreByteBufUtils {
newBuf.writeBytes(buf); newBuf.writeBytes(buf);
return newBuf; return newBuf;
} }
/**
* Creates a {@link ByteBuf} that will have the best performance with the specified
* {@code nativeStuff}.
*
* @param alloc the {@link ByteBufAllocator} to use
* @param nativeStuff the native we are working with
* @return a buffer compatible with the native
*/
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff) {
return nativeStuff.isNative() ? alloc.directBuffer() : alloc.heapBuffer();
}
/**
* Creates a {@link ByteBuf} that will have the best performance with the specified
* {@code nativeStuff}.
*
* @param alloc the {@link ByteBufAllocator} to use
* @param nativeStuff the native we are working with
* @param initialCapacity the initial capacity to allocate
* @return a buffer compatible with the native
*/
public static ByteBuf preferredBuffer(ByteBufAllocator alloc, Native nativeStuff,
int initialCapacity) {
return nativeStuff.isNative() ? alloc.directBuffer(initialCapacity) : alloc
.heapBuffer(initialCapacity);
}
} }

Datei anzeigen

@ -9,7 +9,9 @@ import com.velocitypowered.natives.util.Natives;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.io.IOException;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -18,9 +20,12 @@ import org.junit.jupiter.api.condition.EnabledOnOs;
class VelocityCompressorTest { class VelocityCompressorTest {
private static byte[] TEST_DATA = new byte[1 << 14];
@BeforeAll @BeforeAll
static void checkNatives() { static void checkNatives() throws IOException {
Natives.compress.getLoadedVariant(); Natives.compress.getLoadedVariant();
new Random(1).nextBytes(TEST_DATA);
} }
@Test @Test
@ -31,25 +36,30 @@ class VelocityCompressorTest {
compressor.dispose(); compressor.dispose();
fail("Loaded regular compressor"); fail("Loaded regular compressor");
} }
check(compressor); check(compressor, () -> Unpooled.directBuffer(TEST_DATA.length));
} }
@Test @Test
void javaIntegrityCheck() throws DataFormatException { void javaIntegrityCheckDirect() throws DataFormatException {
VelocityCompressor compressor = JavaVelocityCompressor.FACTORY VelocityCompressor compressor = JavaVelocityCompressor.FACTORY
.create(Deflater.DEFAULT_COMPRESSION); .create(Deflater.DEFAULT_COMPRESSION);
check(compressor); check(compressor, () -> Unpooled.directBuffer(TEST_DATA.length));
} }
private void check(VelocityCompressor compressor) throws DataFormatException { @Test
ByteBuf source = Unpooled.directBuffer(); void javaIntegrityCheckHeap() throws DataFormatException {
ByteBuf dest = Unpooled.directBuffer(); VelocityCompressor compressor = JavaVelocityCompressor.FACTORY
ByteBuf decompressed = Unpooled.directBuffer(); .create(Deflater.DEFAULT_COMPRESSION);
check(compressor, () -> Unpooled.buffer(TEST_DATA.length));
}
Random random = new Random(1); private void check(VelocityCompressor compressor, Supplier<ByteBuf> bufSupplier)
byte[] randomBytes = new byte[1 << 16]; throws DataFormatException {
random.nextBytes(randomBytes); ByteBuf source = bufSupplier.get();
source.writeBytes(randomBytes); ByteBuf dest = bufSupplier.get();
ByteBuf decompressed = bufSupplier.get();
source.writeBytes(TEST_DATA);
try { try {
compressor.deflate(source, dest); compressor.deflate(source, dest);

Datei anzeigen

@ -9,6 +9,7 @@ import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.security.GeneralSecurityException; import java.security.GeneralSecurityException;
import java.util.Random; import java.util.Random;
import java.util.function.Supplier;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
@ -16,11 +17,16 @@ import org.junit.jupiter.api.Test;
class VelocityCipherTest { class VelocityCipherTest {
private static final int ENCRYPT_DATA_SIZE = 1 << 16; private static final int ENCRYPT_DATA_SIZE = 1 << 14;
private static byte[] TEST_DATA = new byte[ENCRYPT_DATA_SIZE];;
private static final byte[] AES_KEY = new byte[16];
@BeforeAll @BeforeAll
static void checkNatives() { static void checkNatives() {
Natives.cipher.getLoadedVariant(); Natives.cipher.getLoadedVariant();
Random random = new Random(1);
random.nextBytes(TEST_DATA);
random.nextBytes(AES_KEY);
} }
@Test @Test
@ -30,30 +36,30 @@ class VelocityCipherTest {
if (factory == JavaVelocityCipher.FACTORY) { if (factory == JavaVelocityCipher.FACTORY) {
fail("Loaded regular cipher"); fail("Loaded regular cipher");
} }
check(factory); check(factory, Unpooled::directBuffer);
} }
@Test @Test
void javaIntegrityCheck() throws GeneralSecurityException { void javaIntegrityCheckDirect() throws GeneralSecurityException {
check(JavaVelocityCipher.FACTORY); check(JavaVelocityCipher.FACTORY, Unpooled::directBuffer);
} }
private void check(VelocityCipherFactory factory) throws GeneralSecurityException { @Test
void javaIntegrityCheckHeap() throws GeneralSecurityException {
check(JavaVelocityCipher.FACTORY, Unpooled::buffer);
}
private void check(VelocityCipherFactory factory, Supplier<ByteBuf> bufSupplier)
throws GeneralSecurityException {
// Generate a random 16-byte key. // Generate a random 16-byte key.
Random random = new Random(1); VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(AES_KEY, "AES"));
byte[] key = new byte[16]; VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(AES_KEY, "AES"));
random.nextBytes(key);
VelocityCipher decrypt = factory.forDecryption(new SecretKeySpec(key, "AES")); ByteBuf source = bufSupplier.get();
VelocityCipher encrypt = factory.forEncryption(new SecretKeySpec(key, "AES")); ByteBuf dest = bufSupplier.get();
ByteBuf decryptionBuf = bufSupplier.get();
ByteBuf source = Unpooled.directBuffer(ENCRYPT_DATA_SIZE); source.writeBytes(TEST_DATA);
ByteBuf dest = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
ByteBuf decryptionBuf = Unpooled.directBuffer(ENCRYPT_DATA_SIZE);
byte[] randomBytes = new byte[ENCRYPT_DATA_SIZE];
random.nextBytes(randomBytes);
source.writeBytes(randomBytes);
try { try {
encrypt.process(source, dest); encrypt.process(source, dest);

Datei anzeigen

@ -1,9 +1,10 @@
package com.velocitypowered.proxy.protocol.netty; package com.velocitypowered.proxy.protocol.netty;
import static com.velocitypowered.natives.util.MoreByteBufUtils.ensureCompatible;
import static com.velocitypowered.natives.util.MoreByteBufUtils.preferredBuffer;
import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame; import static com.velocitypowered.proxy.protocol.util.NettyPreconditions.checkFrame;
import com.velocitypowered.natives.compression.VelocityCompressor; import com.velocitypowered.natives.compression.VelocityCompressor;
import com.velocitypowered.natives.util.MoreByteBufUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -35,9 +36,9 @@ public class MinecraftCompressDecoder extends MessageToMessageDecoder<ByteBuf> {
checkFrame(expectedUncompressedSize >= threshold, checkFrame(expectedUncompressedSize >= threshold,
"Uncompressed size %s is greater than threshold %s", "Uncompressed size %s is greater than threshold %s",
expectedUncompressedSize, threshold); expectedUncompressedSize, threshold);
ByteBuf compatibleIn = MoreByteBufUtils.ensureCompatible(ctx.alloc(), compressor, in); ByteBuf compatibleIn = ensureCompatible(ctx.alloc(), compressor, in);
ByteBuf uncompressed = ctx.alloc().directBuffer(Math.min(expectedUncompressedSize, int initialCapacity = Math.min(expectedUncompressedSize, MAXIMUM_INITIAL_BUFFER_SIZE);
MAXIMUM_INITIAL_BUFFER_SIZE)); ByteBuf uncompressed = preferredBuffer(ctx.alloc(), compressor, initialCapacity);
try { try {
compressor.inflate(compatibleIn, uncompressed); compressor.inflate(compatibleIn, uncompressed);
checkFrame(expectedUncompressedSize == uncompressed.readableBytes(), checkFrame(expectedUncompressedSize == uncompressed.readableBytes(),

Datei anzeigen

@ -38,11 +38,9 @@ public class MinecraftCompressEncoder extends MessageToByteEncoder<ByteBuf> {
@Override @Override
protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect) protected ByteBuf allocateBuffer(ChannelHandlerContext ctx, ByteBuf msg, boolean preferDirect)
throws Exception { throws Exception {
if (msg.readableBytes() <= threshold) { int initialBufferSize = msg.readableBytes() <= threshold ? msg.readableBytes() + 1 :
return ctx.alloc().directBuffer(msg.readableBytes() + 1); msg.readableBytes() / 3;
} return MoreByteBufUtils.preferredBuffer(ctx.alloc(), compressor, initialBufferSize);
// A reasonable assumption about compression savings
return ctx.alloc().directBuffer(msg.readableBytes() / 3);
} }
@Override @Override