Commit 28a03e7e authored by Nick Terrell's avatar Nick Terrell Committed by Facebook Github Bot

Support negative compression levels

Summary:
Folly uses level [-1, -3] for the default/fastest/best compression levels.
Introduce a `ZSTD_FAST` codec to use negative compression levels.

Reviewed By: chipturner

Differential Revision: D8529340

fbshipit-source-id: 7fb7ff65f08d4dcab009c2eb073920b53055ed9b
parent abc3bfdf
...@@ -1507,6 +1507,22 @@ static int zstdConvertLevel(int level) { ...@@ -1507,6 +1507,22 @@ static int zstdConvertLevel(int level) {
return level; return level;
} }
static int zstdFastConvertLevel(int level) {
switch (level) {
case COMPRESSION_LEVEL_FASTEST:
return -5;
case COMPRESSION_LEVEL_DEFAULT:
return -1;
case COMPRESSION_LEVEL_BEST:
return -1;
}
if (level < 1) {
throw std::invalid_argument(
to<std::string>("ZSTD: invalid level: ", level));
}
return -level;
}
std::unique_ptr<Codec> getZstdCodec(int level, CodecType type) { std::unique_ptr<Codec> getZstdCodec(int level, CodecType type) {
DCHECK(type == CodecType::ZSTD); DCHECK(type == CodecType::ZSTD);
return zstd::getCodec(zstd::Options(zstdConvertLevel(level))); return zstd::getCodec(zstd::Options(zstdConvertLevel(level)));
...@@ -1517,6 +1533,16 @@ std::unique_ptr<StreamCodec> getZstdStreamCodec(int level, CodecType type) { ...@@ -1517,6 +1533,16 @@ std::unique_ptr<StreamCodec> getZstdStreamCodec(int level, CodecType type) {
return zstd::getStreamCodec(zstd::Options(zstdConvertLevel(level))); return zstd::getStreamCodec(zstd::Options(zstdConvertLevel(level)));
} }
std::unique_ptr<Codec> getZstdFastCodec(int level, CodecType type) {
DCHECK(type == CodecType::ZSTD_FAST);
return zstd::getCodec(zstd::Options(zstdFastConvertLevel(level)));
}
std::unique_ptr<StreamCodec> getZstdFastStreamCodec(int level, CodecType type) {
DCHECK(type == CodecType::ZSTD_FAST);
return zstd::getStreamCodec(zstd::Options(zstdFastConvertLevel(level)));
}
#endif // FOLLY_HAVE_LIBZSTD #endif // FOLLY_HAVE_LIBZSTD
#if FOLLY_HAVE_LIBBZ2 #if FOLLY_HAVE_LIBBZ2
...@@ -2027,6 +2053,12 @@ constexpr Factory ...@@ -2027,6 +2053,12 @@ constexpr Factory
#else #else
{}, {},
#endif #endif
#if FOLLY_HAVE_LIBZSTD
{getZstdFastCodec, getZstdFastStreamCodec},
#else
{},
#endif
}; };
Factory const& getFactory(CodecType type) { Factory const& getFactory(CodecType type) {
......
...@@ -80,6 +80,8 @@ enum class CodecType { ...@@ -80,6 +80,8 @@ enum class CodecType {
/** /**
* Use ZSTD compression. * Use ZSTD compression.
* Levels supported: 1 = fast, ..., 19 = best; default = 3
* Use ZSTD_FAST for the fastest zstd compression (negative levels).
*/ */
ZSTD = 8, ZSTD = 8,
...@@ -102,7 +104,20 @@ enum class CodecType { ...@@ -102,7 +104,20 @@ enum class CodecType {
*/ */
BZIP2 = 11, BZIP2 = 11,
NUM_CODEC_TYPES = 12, /**
* Use ZSTD compression with a negative compression level (1=-1, 2=-2, ...).
* Higher compression levels mean faster.
* Level 1 is around the same speed as Snappy with better compression.
* Level 5 is around the same speed as LZ4 with slightly worse compression.
* Each level gains about 6-15% speed and loses 3-7% compression.
* Decompression speed improves for each level, and level 1 decompression
* speed is around 25% faster than ZSTD.
* This codec is fully compatible with ZSTD.
* Levels supported: 1 = best, ..., 5 = fast; default = 1
*/
ZSTD_FAST = 12,
NUM_CODEC_TYPES = 13,
}; };
class Codec { class Codec {
......
...@@ -120,8 +120,15 @@ bool ZSTDStreamCodec::canUncompress(const IOBuf* data, Optional<uint64_t>) ...@@ -120,8 +120,15 @@ bool ZSTDStreamCodec::canUncompress(const IOBuf* data, Optional<uint64_t>)
return dataStartsWithLE(data, kZSTDMagicLE); return dataStartsWithLE(data, kZSTDMagicLE);
} }
CodecType codecType(Options const& options) {
int const level = options.level();
DCHECK_NE(level, 0);
return level > 0 ? CodecType::ZSTD : CodecType::ZSTD_FAST;
}
ZSTDStreamCodec::ZSTDStreamCodec(Options options) ZSTDStreamCodec::ZSTDStreamCodec(Options options)
: StreamCodec(CodecType::ZSTD), options_(std::move(options)) {} : StreamCodec(codecType(options), options.level()),
options_(std::move(options)) {}
bool ZSTDStreamCodec::doNeedsUncompressedLength() const { bool ZSTDStreamCodec::doNeedsUncompressedLength() const {
return false; return false;
...@@ -228,7 +235,7 @@ bool ZSTDStreamCodec::doUncompressStream( ...@@ -228,7 +235,7 @@ bool ZSTDStreamCodec::doUncompressStream(
} // namespace } // namespace
Options::Options(int level) : params_(ZSTD_createCCtxParams()) { Options::Options(int level) : params_(ZSTD_createCCtxParams()), level_(level) {
if (params_ == nullptr) { if (params_ == nullptr) {
throw std::bad_alloc{}; throw std::bad_alloc{};
} }
...@@ -238,10 +245,17 @@ Options::Options(int level) : params_(ZSTD_createCCtxParams()) { ...@@ -238,10 +245,17 @@ Options::Options(int level) : params_(ZSTD_createCCtxParams()) {
zstdThrowIfError(ZSTD_initCCtxParams(params_.get(), level)); zstdThrowIfError(ZSTD_initCCtxParams(params_.get(), level));
set(ZSTD_p_contentSizeFlag, 1); set(ZSTD_p_contentSizeFlag, 1);
#endif #endif
// zstd-1.3.4 is buggy and only disables Huffman decompression for negative
// compression levels if this call is present. This call is begign in other
// versions.
set(ZSTD_p_compressionLevel, level);
} }
void Options::set(ZSTD_cParameter param, unsigned value) { void Options::set(ZSTD_cParameter param, unsigned value) {
zstdThrowIfError(ZSTD_CCtxParam_setParameter(params_.get(), param, value)); zstdThrowIfError(ZSTD_CCtxParam_setParameter(params_.get(), param, value));
if (param == ZSTD_p_compressionLevel) {
level_ = static_cast<int>(value);
}
} }
/* static */ void Options::freeCCtxParams(ZSTD_CCtx_params* params) { /* static */ void Options::freeCCtxParams(ZSTD_CCtx_params* params) {
......
...@@ -64,6 +64,11 @@ class Options { ...@@ -64,6 +64,11 @@ class Options {
return params_.get(); return params_.get();
} }
/// Get the compression level.
int level() const {
return level_;
}
/// Get the maximum window size. /// Get the maximum window size.
size_t maxWindowSize() const { size_t maxWindowSize() const {
return maxWindowSize_; return maxWindowSize_;
...@@ -76,6 +81,7 @@ class Options { ...@@ -76,6 +81,7 @@ class Options {
folly::static_function_deleter<ZSTD_CCtx_params, &freeCCtxParams>> folly::static_function_deleter<ZSTD_CCtx_params, &freeCCtxParams>>
params_; params_;
size_t maxWindowSize_{0}; size_t maxWindowSize_{0};
int level_;
}; };
/// Get a zstd Codec with the given options. /// Get a zstd Codec with the given options.
......
...@@ -1408,6 +1408,22 @@ TEST(ZstdTest, CustomOptions) { ...@@ -1408,6 +1408,22 @@ TEST(ZstdTest, CustomOptions) {
} }
} }
TEST(ZstdTest, NegativeLevels) {
EXPECT_EQ(zstd::Options(1).level(), 1);
EXPECT_EQ(zstd::Options(-1).level(), -1);
auto const original = std::string(
reinterpret_cast<const char*>(randomDataHolder.data(16348).data()),
16348);
auto const plusCompressed =
zstd::getCodec(zstd::Options(1))->compress(original);
auto const minusCompressed =
zstd::getCodec(zstd::Options(-100))->compress(original);
EXPECT_GT(minusCompressed.size(), plusCompressed.size());
auto codec = getCodec(CodecType::ZSTD);
auto const uncompressed = codec->uncompress(minusCompressed);
EXPECT_EQ(original, uncompressed);
}
#endif #endif
#if FOLLY_HAVE_LIBZ #if FOLLY_HAVE_LIBZ
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment