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:
Ursprung
095a478440
Commit
8588e7f1fe
@ -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.
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Binäre Datei nicht angezeigt.
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren