mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Nick Terrell](/assets/img/avatar_default.png)
Add zstd compression and decompression kernel modules. zstd offers a wide varity of compression speed and quality trade-offs. It can compress at speeds approaching lz4, and quality approaching lzma. zstd decompressions at speeds more than twice as fast as zlib, and decompression speed remains roughly the same across all compression levels. The code was ported from the upstream zstd source repository. The `linux/zstd.h` header was modified to match linux kernel style. The cross-platform and allocation code was stripped out. Instead zstd requires the caller to pass a preallocated workspace. The source files were clang-formatted [1] to match the Linux Kernel style as much as possible. Otherwise, the code was unmodified. We would like to avoid as much further manual modification to the source code as possible, so it will be easier to keep the kernel zstd up to date. I benchmarked zstd compression as a special character device. I ran zstd and zlib compression at several levels, as well as performing no compression, which measure the time spent copying the data to kernel space. Data is passed to the compresser 4096 B at a time. The benchmark file is located in the upstream zstd source repository under `contrib/linux-kernel/zstd_compress_test.c` [2]. I ran the benchmarks on a Ubuntu 14.04 VM with 2 cores and 4 GiB of RAM. The VM is running on a MacBook Pro with a 3.1 GHz Intel Core i7 processor, 16 GB of RAM, and a SSD. I benchmarked using `silesia.tar` [3], which is 211,988,480 B large. Run the following commands for the benchmark: sudo modprobe zstd_compress_test sudo mknod zstd_compress_test c 245 0 sudo cp silesia.tar zstd_compress_test The time is reported by the time of the userland `cp`. The MB/s is computed with 1,536,217,008 B / time(buffer size, hash) which includes the time to copy from userland. The Adjusted MB/s is computed with 1,536,217,088 B / (time(buffer size, hash) - time(buffer size, none)). The memory reported is the amount of memory the compressor requests. | Method | Size (B) | Time (s) | Ratio | MB/s | Adj MB/s | Mem (MB) | |----------|----------|----------|-------|---------|----------|----------| | none | 11988480 | 0.100 | 1 | 2119.88 | - | - | | zstd -1 | 73645762 | 1.044 | 2.878 | 203.05 | 224.56 | 1.23 | | zstd -3 | 66988878 | 1.761 | 3.165 | 120.38 | 127.63 | 2.47 | | zstd -5 | 65001259 | 2.563 | 3.261 | 82.71 | 86.07 | 2.86 | | zstd -10 | 60165346 | 13.242 | 3.523 | 16.01 | 16.13 | 13.22 | | zstd -15 | 58009756 | 47.601 | 3.654 | 4.45 | 4.46 | 21.61 | | zstd -19 | 54014593 | 102.835 | 3.925 | 2.06 | 2.06 | 60.15 | | zlib -1 | 77260026 | 2.895 | 2.744 | 73.23 | 75.85 | 0.27 | | zlib -3 | 72972206 | 4.116 | 2.905 | 51.50 | 52.79 | 0.27 | | zlib -6 | 68190360 | 9.633 | 3.109 | 22.01 | 22.24 | 0.27 | | zlib -9 | 67613382 | 22.554 | 3.135 | 9.40 | 9.44 | 0.27 | I benchmarked zstd decompression using the same method on the same machine. The benchmark file is located in the upstream zstd repo under `contrib/linux-kernel/zstd_decompress_test.c` [4]. The memory reported is the amount of memory required to decompress data compressed with the given compression level. If you know the maximum size of your input, you can reduce the memory usage of decompression irrespective of the compression level. | Method | Time (s) | MB/s | Adjusted MB/s | Memory (MB) | |----------|----------|---------|---------------|-------------| | none | 0.025 | 8479.54 | - | - | | zstd -1 | 0.358 | 592.15 | 636.60 | 0.84 | | zstd -3 | 0.396 | 535.32 | 571.40 | 1.46 | | zstd -5 | 0.396 | 535.32 | 571.40 | 1.46 | | zstd -10 | 0.374 | 566.81 | 607.42 | 2.51 | | zstd -15 | 0.379 | 559.34 | 598.84 | 4.61 | | zstd -19 | 0.412 | 514.54 | 547.77 | 8.80 | | zlib -1 | 0.940 | 225.52 | 231.68 | 0.04 | | zlib -3 | 0.883 | 240.08 | 247.07 | 0.04 | | zlib -6 | 0.844 | 251.17 | 258.84 | 0.04 | | zlib -9 | 0.837 | 253.27 | 287.64 | 0.04 | Tested in userland using the test-suite in the zstd repo under `contrib/linux-kernel/test/UserlandTest.cpp` [5] by mocking the kernel functions. Fuzz tested using libfuzzer [6] with the fuzz harnesses under `contrib/linux-kernel/test/{RoundTripCrash.c,DecompressCrash.c}` [7] [8] with ASAN, UBSAN, and MSAN. Additionaly, it was tested while testing the BtrFS and SquashFS patches coming next. [1] https://clang.llvm.org/docs/ClangFormat.html [2] https://github.com/facebook/zstd/blob/dev/contrib/linux-kernel/zstd_compress_test.c [3] http://sun.aei.polsl.pl/~sdeor/index.php?page=silesia [4] https://github.com/facebook/zstd/blob/dev/contrib/linux-kernel/zstd_decompress_test.c [5] https://github.com/facebook/zstd/blob/dev/contrib/linux-kernel/test/UserlandTest.cpp [6] http://llvm.org/docs/LibFuzzer.html [7] https://github.com/facebook/zstd/blob/dev/contrib/linux-kernel/test/RoundTripCrash.c [8] https://github.com/facebook/zstd/blob/dev/contrib/linux-kernel/test/DecompressCrash.c zstd source repository: https://github.com/facebook/zstd Signed-off-by: Nick Terrell <terrelln@fb.com> Signed-off-by: Chris Mason <clm@fb.com>
151 lines
4.7 KiB
C
151 lines
4.7 KiB
C
/**
|
|
* Copyright (c) 2016-present, Yann Collet, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of https://github.com/facebook/zstd.
|
|
* An additional grant of patent rights can be found in the PATENTS file in the
|
|
* same directory.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License version 2 as published by the
|
|
* Free Software Foundation. This program is dual-licensed; you may select
|
|
* either version 2 of the GNU General Public License ("GPL") or BSD license
|
|
* ("BSD").
|
|
*/
|
|
|
|
#ifndef MEM_H_MODULE
|
|
#define MEM_H_MODULE
|
|
|
|
/*-****************************************
|
|
* Dependencies
|
|
******************************************/
|
|
#include <asm/unaligned.h>
|
|
#include <linux/string.h> /* memcpy */
|
|
#include <linux/types.h> /* size_t, ptrdiff_t */
|
|
|
|
/*-****************************************
|
|
* Compiler specifics
|
|
******************************************/
|
|
#define ZSTD_STATIC static __inline __attribute__((unused))
|
|
|
|
/*-**************************************************************
|
|
* Basic Types
|
|
*****************************************************************/
|
|
typedef uint8_t BYTE;
|
|
typedef uint16_t U16;
|
|
typedef int16_t S16;
|
|
typedef uint32_t U32;
|
|
typedef int32_t S32;
|
|
typedef uint64_t U64;
|
|
typedef int64_t S64;
|
|
typedef ptrdiff_t iPtrDiff;
|
|
typedef uintptr_t uPtrDiff;
|
|
|
|
/*-**************************************************************
|
|
* Memory I/O
|
|
*****************************************************************/
|
|
ZSTD_STATIC unsigned ZSTD_32bits(void) { return sizeof(size_t) == 4; }
|
|
ZSTD_STATIC unsigned ZSTD_64bits(void) { return sizeof(size_t) == 8; }
|
|
|
|
#if defined(__LITTLE_ENDIAN)
|
|
#define ZSTD_LITTLE_ENDIAN 1
|
|
#else
|
|
#define ZSTD_LITTLE_ENDIAN 0
|
|
#endif
|
|
|
|
ZSTD_STATIC unsigned ZSTD_isLittleEndian(void) { return ZSTD_LITTLE_ENDIAN; }
|
|
|
|
ZSTD_STATIC U16 ZSTD_read16(const void *memPtr) { return get_unaligned((const U16 *)memPtr); }
|
|
|
|
ZSTD_STATIC U32 ZSTD_read32(const void *memPtr) { return get_unaligned((const U32 *)memPtr); }
|
|
|
|
ZSTD_STATIC U64 ZSTD_read64(const void *memPtr) { return get_unaligned((const U64 *)memPtr); }
|
|
|
|
ZSTD_STATIC size_t ZSTD_readST(const void *memPtr) { return get_unaligned((const size_t *)memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_write16(void *memPtr, U16 value) { put_unaligned(value, (U16 *)memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_write32(void *memPtr, U32 value) { put_unaligned(value, (U32 *)memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_write64(void *memPtr, U64 value) { put_unaligned(value, (U64 *)memPtr); }
|
|
|
|
/*=== Little endian r/w ===*/
|
|
|
|
ZSTD_STATIC U16 ZSTD_readLE16(const void *memPtr) { return get_unaligned_le16(memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeLE16(void *memPtr, U16 val) { put_unaligned_le16(val, memPtr); }
|
|
|
|
ZSTD_STATIC U32 ZSTD_readLE24(const void *memPtr) { return ZSTD_readLE16(memPtr) + (((const BYTE *)memPtr)[2] << 16); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeLE24(void *memPtr, U32 val)
|
|
{
|
|
ZSTD_writeLE16(memPtr, (U16)val);
|
|
((BYTE *)memPtr)[2] = (BYTE)(val >> 16);
|
|
}
|
|
|
|
ZSTD_STATIC U32 ZSTD_readLE32(const void *memPtr) { return get_unaligned_le32(memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeLE32(void *memPtr, U32 val32) { put_unaligned_le32(val32, memPtr); }
|
|
|
|
ZSTD_STATIC U64 ZSTD_readLE64(const void *memPtr) { return get_unaligned_le64(memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeLE64(void *memPtr, U64 val64) { put_unaligned_le64(val64, memPtr); }
|
|
|
|
ZSTD_STATIC size_t ZSTD_readLEST(const void *memPtr)
|
|
{
|
|
if (ZSTD_32bits())
|
|
return (size_t)ZSTD_readLE32(memPtr);
|
|
else
|
|
return (size_t)ZSTD_readLE64(memPtr);
|
|
}
|
|
|
|
ZSTD_STATIC void ZSTD_writeLEST(void *memPtr, size_t val)
|
|
{
|
|
if (ZSTD_32bits())
|
|
ZSTD_writeLE32(memPtr, (U32)val);
|
|
else
|
|
ZSTD_writeLE64(memPtr, (U64)val);
|
|
}
|
|
|
|
/*=== Big endian r/w ===*/
|
|
|
|
ZSTD_STATIC U32 ZSTD_readBE32(const void *memPtr) { return get_unaligned_be32(memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeBE32(void *memPtr, U32 val32) { put_unaligned_be32(val32, memPtr); }
|
|
|
|
ZSTD_STATIC U64 ZSTD_readBE64(const void *memPtr) { return get_unaligned_be64(memPtr); }
|
|
|
|
ZSTD_STATIC void ZSTD_writeBE64(void *memPtr, U64 val64) { put_unaligned_be64(val64, memPtr); }
|
|
|
|
ZSTD_STATIC size_t ZSTD_readBEST(const void *memPtr)
|
|
{
|
|
if (ZSTD_32bits())
|
|
return (size_t)ZSTD_readBE32(memPtr);
|
|
else
|
|
return (size_t)ZSTD_readBE64(memPtr);
|
|
}
|
|
|
|
ZSTD_STATIC void ZSTD_writeBEST(void *memPtr, size_t val)
|
|
{
|
|
if (ZSTD_32bits())
|
|
ZSTD_writeBE32(memPtr, (U32)val);
|
|
else
|
|
ZSTD_writeBE64(memPtr, (U64)val);
|
|
}
|
|
|
|
/* function safe only for comparisons */
|
|
ZSTD_STATIC U32 ZSTD_readMINMATCH(const void *memPtr, U32 length)
|
|
{
|
|
switch (length) {
|
|
default:
|
|
case 4: return ZSTD_read32(memPtr);
|
|
case 3:
|
|
if (ZSTD_isLittleEndian())
|
|
return ZSTD_read32(memPtr) << 8;
|
|
else
|
|
return ZSTD_read32(memPtr) >> 8;
|
|
}
|
|
}
|
|
|
|
#endif /* MEM_H_MODULE */
|