Switch out Cloudflare zlib for libdeflate.
libdeflate is significantly faster than vanilla zlib, zlib-ng, and Cloudflare zlib. It is also MIT-licensed (so no licensing concerns). In addition, it simplifies a lot of the native code (something that's been tricky to get right). While we're at it, I have also taken the time to fine-time compression in Velocity in general. Thanks to this work, native compression only requires one JNI call, an improvement from the more than 2 (sometimes up to 5) that were possible before. This optimization also extends to the existing Java compressors, though they require potentially two JNI calls.
This commit is contained in:
@@ -54,7 +54,8 @@ public class Java11VelocityCompressor implements VelocityCompressor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||
throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
// We (probably) can't nicely deal with >=1 buffer nicely, so let's scream loudly.
|
||||
@@ -67,7 +68,7 @@ public class Java11VelocityCompressor implements VelocityCompressor {
|
||||
|
||||
while (!inflater.finished() && inflater.getBytesRead() < source.readableBytes()) {
|
||||
if (!destination.isWritable()) {
|
||||
ensureMaxSize(destination, max);
|
||||
ensureMaxSize(destination, uncompressedSize);
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,20 +25,21 @@ public class JavaVelocityCompressor implements VelocityCompressor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||
throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
|
||||
final int available = source.readableBytes();
|
||||
this.setInflaterInput(source);
|
||||
|
||||
if (destination.hasArray()) {
|
||||
this.inflateDestinationIsHeap(destination, available, max);
|
||||
this.inflateDestinationIsHeap(destination, available, uncompressedSize);
|
||||
} else {
|
||||
if (buf.length == 0) {
|
||||
buf = new byte[ZLIB_BUFFER_SIZE];
|
||||
}
|
||||
while (!inflater.finished() && inflater.getBytesRead() < available) {
|
||||
ensureMaxSize(destination, max);
|
||||
ensureMaxSize(destination, uncompressedSize);
|
||||
int read = inflater.inflate(buf);
|
||||
destination.writeBytes(buf, 0, read);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public class LibdeflateVelocityCompressor implements VelocityCompressor {
|
||||
|
||||
public static final VelocityCompressorFactory FACTORY = LibdeflateVelocityCompressor::new;
|
||||
|
||||
private final NativeZlibInflate inflate = new NativeZlibInflate();
|
||||
private final long inflateCtx;
|
||||
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
|
||||
private final long deflateCtx;
|
||||
private boolean disposed = false;
|
||||
|
||||
private LibdeflateVelocityCompressor(int level) {
|
||||
int correctedLevel = level == -1 ? 6 : level;
|
||||
if (correctedLevel > 12 || correctedLevel < 1) {
|
||||
throw new IllegalArgumentException("Invalid compression level " + level);
|
||||
}
|
||||
|
||||
this.inflateCtx = inflate.init();
|
||||
this.deflateCtx = deflate.init(level == -1 ? 6 : level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||
throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
// libdeflate recommends we work with a known uncompressed size - so we work strictly within
|
||||
// those parameters. If the uncompressed size doesn't match the compressed size, then we will
|
||||
// throw an exception from native code.
|
||||
destination.ensureWritable(uncompressedSize);
|
||||
|
||||
long sourceAddress = source.memoryAddress() + source.readerIndex();
|
||||
long destinationAddress = destination.memoryAddress() + destination.writerIndex();
|
||||
|
||||
inflate.process(inflateCtx, sourceAddress, source.readableBytes(), destinationAddress,
|
||||
uncompressedSize);
|
||||
destination.writerIndex(destination.writerIndex() + uncompressedSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (true) {
|
||||
long sourceAddress = source.memoryAddress() + source.readerIndex();
|
||||
long destinationAddress = destination.memoryAddress() + destination.writerIndex();
|
||||
|
||||
int produced = deflate.process(deflateCtx, sourceAddress, source.readableBytes(),
|
||||
destinationAddress, destination.writableBytes());
|
||||
if (produced > 0) {
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
return;
|
||||
}
|
||||
|
||||
// Insufficient room - enlarge the buffer.
|
||||
destination.capacity(destination.capacity() * 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
inflate.free(inflateCtx);
|
||||
deflate.free(deflateCtx);
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.DIRECT_REQUIRED;
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
import static com.velocitypowered.natives.compression.CompressorUtils.ZLIB_BUFFER_SIZE;
|
||||
import static com.velocitypowered.natives.compression.CompressorUtils.ensureMaxSize;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.util.BufferPreference;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
public class NativeVelocityCompressor implements VelocityCompressor {
|
||||
|
||||
public static final VelocityCompressorFactory FACTORY = NativeVelocityCompressor::new;
|
||||
|
||||
private final NativeZlibInflate inflate = new NativeZlibInflate();
|
||||
private final long inflateCtx;
|
||||
private final NativeZlibDeflate deflate = new NativeZlibDeflate();
|
||||
private final long deflateCtx;
|
||||
private boolean disposed = false;
|
||||
|
||||
private NativeVelocityCompressor(int level) {
|
||||
this.inflateCtx = inflate.init();
|
||||
this.deflateCtx = deflate.init(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (!inflate.finished && source.isReadable()) {
|
||||
if (!destination.isWritable()) {
|
||||
ensureMaxSize(destination, max);
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = inflate.process(inflateCtx, source.memoryAddress() + source.readerIndex(),
|
||||
source.readableBytes(), destination.memoryAddress() + destination.writerIndex(),
|
||||
destination.writableBytes());
|
||||
source.readerIndex(source.readerIndex() + inflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
inflate.reset(inflateCtx);
|
||||
inflate.consumed = 0;
|
||||
inflate.finished = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException {
|
||||
ensureNotDisposed();
|
||||
source.memoryAddress();
|
||||
destination.memoryAddress();
|
||||
|
||||
while (!deflate.finished) {
|
||||
if (!destination.isWritable()) {
|
||||
destination.ensureWritable(ZLIB_BUFFER_SIZE);
|
||||
}
|
||||
int produced = deflate.process(deflateCtx, source.memoryAddress() + source.readerIndex(),
|
||||
source.readableBytes(),
|
||||
destination.memoryAddress() + destination.writerIndex(), destination.writableBytes(),
|
||||
true);
|
||||
source.readerIndex(source.readerIndex() + deflate.consumed);
|
||||
destination.writerIndex(destination.writerIndex() + produced);
|
||||
}
|
||||
|
||||
deflate.reset(deflateCtx);
|
||||
deflate.consumed = 0;
|
||||
deflate.finished = false;
|
||||
}
|
||||
|
||||
private void ensureNotDisposed() {
|
||||
Preconditions.checkState(!disposed, "Object already disposed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
if (!disposed) {
|
||||
inflate.free(inflateCtx);
|
||||
deflate.free(deflateCtx);
|
||||
}
|
||||
disposed = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferPreference preferredBufferType() {
|
||||
return BufferPreference.DIRECT_REQUIRED;
|
||||
}
|
||||
}
|
||||
@@ -5,21 +5,10 @@ package com.velocitypowered.natives.compression;
|
||||
*/
|
||||
class NativeZlibDeflate {
|
||||
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init(int level);
|
||||
|
||||
native long free(long ctx);
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
int destinationLength, boolean finish);
|
||||
|
||||
native void reset(long ctx);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
int destinationLength);
|
||||
}
|
||||
|
||||
@@ -1,25 +1,16 @@
|
||||
package com.velocitypowered.natives.compression;
|
||||
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
* Represents a native interface for zlib's inflate functions.
|
||||
*/
|
||||
class NativeZlibInflate {
|
||||
|
||||
boolean finished;
|
||||
int consumed;
|
||||
|
||||
native long init();
|
||||
|
||||
native long free(long ctx);
|
||||
|
||||
native int process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
int destinationLength);
|
||||
|
||||
native void reset(long ctx);
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
}
|
||||
|
||||
private static native void initIDs();
|
||||
native boolean process(long ctx, long sourceAddress, int sourceLength, long destinationAddress,
|
||||
int destinationLength) throws DataFormatException;
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import io.netty.buffer.ByteBuf;
|
||||
import java.util.zip.DataFormatException;
|
||||
|
||||
/**
|
||||
* Provides an interface to inflate and deflate {@link ByteBuf}s using zlib.
|
||||
* Provides an interface to inflate and deflate {@link ByteBuf}s using zlib or a compatible
|
||||
* implementation.
|
||||
*/
|
||||
public interface VelocityCompressor extends Disposable, Native {
|
||||
void inflate(ByteBuf source, ByteBuf destination, int max) throws DataFormatException;
|
||||
void inflate(ByteBuf source, ByteBuf destination, int uncompressedSize)
|
||||
throws DataFormatException;
|
||||
|
||||
void deflate(ByteBuf source, ByteBuf destination) throws DataFormatException;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.natives.NativeSetupException;
|
||||
import com.velocitypowered.natives.compression.Java11VelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.JavaVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.NativeVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.LibdeflateVelocityCompressor;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressorFactory;
|
||||
import com.velocitypowered.natives.encryption.JavaVelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.NativeVelocityCipher;
|
||||
@@ -64,10 +64,11 @@ public class Natives {
|
||||
ImmutableList.of(
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.MACOS,
|
||||
copyAndLoadNative("/macosx/velocity-compress.dylib"), "native (macOS)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
LibdeflateVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.LINUX,
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"), "native (Linux amd64)",
|
||||
NativeVelocityCompressor.FACTORY),
|
||||
copyAndLoadNative("/linux_x64/velocity-compress.so"),
|
||||
"libdeflate (Linux amd64)",
|
||||
LibdeflateVelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeConstraints.JAVA_11, () -> {
|
||||
}, "Java 11", () -> Java11VelocityCompressor.FACTORY),
|
||||
new NativeCodeLoader.Variant<>(NativeCodeLoader.ALWAYS, () -> {
|
||||
|
||||
Reference in New Issue
Block a user