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:
Andrew Steinborn
2020-05-24 10:56:26 -04:00
parent 742b8d98cb
commit b3bd773fea
19 changed files with 173 additions and 349 deletions

View File

@@ -1,29 +0,0 @@
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zlib.h>
#include "jni_util.h"
void JNICALL
check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate)
{
int ret = deflate ? deflateEnd(stream) : inflateEnd(stream);
const char *msg = stream->msg;
free((void*) stream);
switch (ret) {
case Z_OK:
break;
case Z_STREAM_ERROR:
if (msg == NULL) {
msg = "stream state inconsistent";
}
// fall-through
case Z_DATA_ERROR:
if (msg == NULL) {
msg = "data was discarded";
}
throwException(env, "java/lang/IllegalArgumentException", msg);
break;
}
}

View File

@@ -1,6 +0,0 @@
#include <jni.h>
#include <stdbool.h>
#include <zlib.h>
void JNICALL
check_zlib_free(JNIEnv *env, z_stream *stream, bool deflate);

View File

@@ -2,56 +2,21 @@
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zlib.h>
#include <libdeflate.h>
#include "jni_util.h"
#include "jni_zlib_common.h"
static jfieldID finishedID;
static jfieldID consumedID;
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_initIDs(JNIEnv *env, jclass cls)
{
finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
consumedID = (*env)->GetFieldID(env, cls, "consumed", "I");
}
JNIEXPORT jlong JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_init(JNIEnv *env,
jobject obj,
jint level)
{
z_stream* stream = calloc(1, sizeof(z_stream));
if (stream == 0) {
struct libdeflate_compressor *compressor = libdeflate_alloc_compressor(level);
if (compressor == NULL) {
// Out of memory!
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream");
return 0;
}
int ret = deflateInit(stream, level);
if (ret == Z_OK) {
return (jlong) stream;
} else {
const char *zlib_msg = stream->msg;
free(stream);
switch (ret) {
case Z_MEM_ERROR:
throwException(env, "java/lang/OutOfMemoryError", "zlib init");
break;
case Z_STREAM_ERROR: {
// Thanks Ken and Ritchie!
char message[32];
snprintf(message, 32, "invalid level %d", level);
throwException(env, "java/lang/IllegalArgumentException", message);
break;
}
default:
throwException(env, "java/util/zip/DataFormatException", zlib_msg);
break;
}
throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate compressor");
return 0;
}
return (jlong) compressor;
}
JNIEXPORT void JNICALL
@@ -59,11 +24,10 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_free(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
check_zlib_free(env, stream, true);
libdeflate_free_compressor((struct libdeflate_compressor *) ctx);
}
JNIEXPORT int JNICALL
JNIEXPORT jboolean JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *env,
jobject obj,
jlong ctx,
@@ -73,38 +37,8 @@ Java_com_velocitypowered_natives_compression_NativeZlibDeflate_process(JNIEnv *e
jint destinationLength,
jboolean finish)
{
z_stream* stream = (z_stream*) ctx;
stream->next_in = (Bytef *) sourceAddress;
stream->next_out = (Bytef *) destinationAddress;
stream->avail_in = sourceLength;
stream->avail_out = destinationLength;
int res = deflate(stream, finish ? Z_FINISH : Z_NO_FLUSH);
switch (res) {
case Z_STREAM_END:
// The stream has ended.
(*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE);
// fall-through
case Z_OK:
// Not yet completed, but progress has been made. Tell Java how many bytes we've processed.
(*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in);
return destinationLength - stream->avail_out;
case Z_BUF_ERROR:
// This is not fatal. Just say we need more data. Usually this applies to the next_out buffer,
// which NativeVelocityCompressor will notice and will expand the buffer.
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", stream->msg);
return 0;
}
}
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibDeflate_reset(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
int ret = deflateReset(stream);
assert(ret == Z_OK);
struct libdeflate_compressor *compressor = (struct libdeflate_compressor *) ctx;
size_t produced = libdeflate_zlib_compress(compressor, (void *) sourceAddress, sourceLength,
(void *) destinationAddress, destinationLength);
return (jlong) produced;
}

View File

@@ -2,50 +2,21 @@
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <zlib.h>
#include <libdeflate.h>
#include "jni_util.h"
#include "jni_zlib_common.h"
static jfieldID finishedID;
static jfieldID consumedID;
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_initIDs(JNIEnv *env, jclass cls)
{
finishedID = (*env)->GetFieldID(env, cls, "finished", "Z");
consumedID = (*env)->GetFieldID(env, cls, "consumed", "I");
}
JNIEXPORT jlong JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_init(JNIEnv *env,
jobject obj)
{
z_stream* stream = calloc(1, sizeof(z_stream));
if (stream == 0) {
struct libdeflate_decompressor *decompress = libdeflate_alloc_decompressor();
if (decompress == NULL) {
// Out of memory!
throwException(env, "java/lang/OutOfMemoryError", "zlib allocate stream");
throwException(env, "java/lang/OutOfMemoryError", "libdeflate allocate decompressor");
return 0;
}
int ret = inflateInit(stream);
if (ret == Z_OK) {
return (jlong) stream;
} else {
const char *zlib_msg = stream->msg;
free(stream);
switch (ret) {
case Z_MEM_ERROR:
throwException(env, "java/lang/OutOfMemoryError", "zlib init");
return 0;
case Z_STREAM_ERROR:
throwException(env, "java/lang/IllegalArgumentException", "stream clobbered?");
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", zlib_msg);
return 0;
}
}
return (jlong) decompress;
}
JNIEXPORT void JNICALL
@@ -53,51 +24,34 @@ Java_com_velocitypowered_natives_compression_NativeZlibInflate_free(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
check_zlib_free(env, stream, false);
libdeflate_free_decompressor((struct libdeflate_decompressor *) ctx);
}
JNIEXPORT int JNICALL
JNIEXPORT jboolean JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_process(JNIEnv *env,
jobject obj,
jlong ctx,
jlong sourceAddress,
jint sourceLength,
jlong destinationAddress,
jint destinationLength)
jint destinationLength,
jlong maximumSize)
{
z_stream* stream = (z_stream*) ctx;
stream->next_in = (Bytef *) sourceAddress;
stream->next_out = (Bytef *) destinationAddress;
stream->avail_in = sourceLength;
stream->avail_out = destinationLength;
struct libdeflate_decompressor *decompress = (struct libdeflate_decompressor *) ctx;
enum libdeflate_result result = libdeflate_zlib_decompress(decompress, (void *) sourceAddress,
sourceLength, (void *) destinationAddress, destinationLength, NULL);
int res = inflate(stream, Z_PARTIAL_FLUSH);
switch (res) {
case Z_STREAM_END:
// The stream has ended
(*env)->SetBooleanField(env, obj, finishedID, JNI_TRUE);
// fall-through
case Z_OK:
// Not yet completed, but progress has been made. Tell Java how many bytes we've processed.
(*env)->SetIntField(env, obj, consumedID, sourceLength - stream->avail_in);
return destinationLength - stream->avail_out;
case Z_BUF_ERROR:
// This is not fatal. Just say we need more data. Usually this applies to the next_out buffer,
// which NativeVelocityCompressor will notice and will expand the buffer.
return 0;
default:
throwException(env, "java/util/zip/DataFormatException", stream->msg);
return 0;
switch (result) {
case LIBDEFLATE_SUCCESS:
// We are happy
return JNI_TRUE;
case LIBDEFLATE_BAD_DATA:
throwException(env, "java/util/zip/DataFormatException", "inflate data is bad");
return JNI_FALSE;
case LIBDEFLATE_SHORT_OUTPUT:
case LIBDEFLATE_INSUFFICIENT_SPACE:
// These cases are the same for us. We expect the full uncompressed size to be known.
throwException(env, "java/util/zip/DataFormatException", "uncompressed size is inaccurate");
return JNI_FALSE;
}
}
JNIEXPORT void JNICALL
Java_com_velocitypowered_natives_compression_NativeZlibInflate_reset(JNIEnv *env,
jobject obj,
jlong ctx)
{
z_stream* stream = (z_stream*) ctx;
int ret = inflateReset(stream);
assert(ret == Z_OK);
}