From 77b9a8f6999c120b7a8857f39c19967f521c276e Mon Sep 17 00:00:00 2001 From: Roman Gershman Date: Sun, 2 Mar 2025 21:29:19 +0200 Subject: [PATCH] chore: introduce DEBUG COMPRESSION (#4620) This function analyzes the compressability of the keys using a single huffman tree. For example, ``` >debug POPULATE 1000000 keyabcdef 10 OK > debug compression 1) max_symbol 2) (integer) 121 3) max_bits 4) (integer) 5 5) raw_size 6) (integer) 7861817 7) compressed_size 8) (integer) 4372270 9) ratio 10) "0.5561398847111297" ``` Signed-off-by: Roman Gershman --- .pre-commit-config.yaml | 1 + docs/build-from-source.md | 12 +- helio | 2 +- src/core/CMakeLists.txt | 4 +- src/huff/LICENSE | 30 +++++ src/huff/README.md | 4 + src/huff/hist.h | 82 ++++++++++++ src/huff/huf.h | 275 ++++++++++++++++++++++++++++++++++++++ src/huff/mem.h | 24 ++++ src/server/CMakeLists.txt | 4 +- src/server/debugcmd.cc | 113 ++++++++++++++++ src/server/debugcmd.h | 1 + 12 files changed, 539 insertions(+), 13 deletions(-) create mode 100644 src/huff/LICENSE create mode 100644 src/huff/README.md create mode 100644 src/huff/hist.h create mode 100644 src/huff/huf.h create mode 100644 src/huff/mem.h diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ea3413e5a..1826eed6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,6 +2,7 @@ default_stages: [commit] exclude: | (?x)( src/redis/.* | + src/huff/.* | contrib/charts/dragonfly/ci/.* | patches/.* ) diff --git a/docs/build-from-source.md b/docs/build-from-source.md index 26f7be821..f5353132f 100644 --- a/docs/build-from-source.md +++ b/docs/build-from-source.md @@ -15,22 +15,22 @@ but you can also run Dragonfly on older kernels as well. On Debian/Ubuntu: ```bash -sudo apt install ninja-build libunwind-dev libboost-fiber-dev libssl-dev \ - autoconf-archive libtool cmake g++ libzstd-dev bison libxml2-dev zlib1g-dev +sudo apt install ninja-build libunwind-dev libboost-context-dev libssl-dev \ + autoconf-archive libtool cmake g++ bison zlib1g-dev ``` On Fedora: ```bash -sudo dnf install -y automake boost-devel g++ git cmake libtool ninja-build libzstd-devel \ - openssl-devel libunwind-devel autoconf-archive patch bison libxml2-devel libstdc++-static +sudo dnf install -y automake boost-devel g++ git cmake libtool ninja-build \ + openssl-devel libunwind-devel autoconf-archive patch bison libstdc++-static ``` On openSUSE: ```bash -sudo zypper install automake boost-devel gcc-c++ git cmake libtool ninja libzstd-devel \ - openssl-devel libunwind-devel autoconf-archive patch bison libxml2-devel \ +sudo zypper install automake boost-devel gcc-c++ git cmake libtool ninja \ + openssl-devel libunwind-devel autoconf-archive patch bison \ libboost_context-devel libboost_system-devel ``` diff --git a/helio b/helio index 875cedd2a..6e29a5af3 160000 --- a/helio +++ b/helio @@ -1 +1 @@ -Subproject commit 875cedd2a0cd084fd15a3d5dbfe20150e19ffcef +Subproject commit 6e29a5af38cb9e99be37469a87c6faac7114f55b diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 8d3af2415..4db2e5eea 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -34,8 +34,6 @@ cxx_link(dash_bench dfly_core redis_test_lib) -find_library(ZSTD_LIB NAMES libzstd.a libzstdstatic.a zstd NAMES_PER_DIR REQUIRED) - cxx_test(dfly_core_test dfly_core TRDP::fast_float ${PCRE2_LIB} ${RE2_LIB} LABELS DFLY) cxx_test(compact_object_test dfly_core LABELS DFLY) cxx_test(extent_tree_test dfly_core LABELS DFLY) @@ -51,7 +49,7 @@ cxx_test(flatbuffers_test dfly_core TRDP::flatbuffers LABELS DFLY) cxx_test(bloom_test dfly_core LABELS DFLY) cxx_test(allocation_tracker_test dfly_core absl::random_random LABELS DFLY) cxx_test(qlist_test dfly_core DATA testdata/list.txt.zst LABELS DFLY) -cxx_test(zstd_test dfly_core ${ZSTD_LIB} LABELS DFLY) +cxx_test(zstd_test dfly_core TRDP::zstd LABELS DFLY) cxx_test(top_keys_test dfly_core LABELS DFLY) if(LIB_PCRE2) diff --git a/src/huff/LICENSE b/src/huff/LICENSE new file mode 100644 index 000000000..75800288c --- /dev/null +++ b/src/huff/LICENSE @@ -0,0 +1,30 @@ +BSD License + +For Zstandard software + +Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook, nor Meta, nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/huff/README.md b/src/huff/README.md new file mode 100644 index 000000000..9a71aacc1 --- /dev/null +++ b/src/huff/README.md @@ -0,0 +1,4 @@ +The code in this folder exposes internal functions that are used by ZSTD. +These functions are part of https://github.com/Cyan4973/FiniteStateEntropy project. + +Since we already link to ZSTD, it is convenient that we get this functionality for free. \ No newline at end of file diff --git a/src/huff/hist.h b/src/huff/hist.h new file mode 100644 index 000000000..b566d159d --- /dev/null +++ b/src/huff/hist.h @@ -0,0 +1,82 @@ +/* ****************************************************************** + * hist : Histogram functions + * part of Finite State Entropy project + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - FSE source repository : https://github.com/Cyan4973/FiniteStateEntropy + * - Public forum : https://groups.google.com/forum/#!forum/lz4c + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. +****************************************************************** */ + +/* --- dependencies --- */ +#include /* size_t */ + + +/* --- simple histogram functions --- */ + +/*! HIST_count(): + * Provides the precise count of each byte within a table 'count'. + * 'count' is a table of unsigned int, of minimum size (*maxSymbolValuePtr+1). + * Updates *maxSymbolValuePtr with actual largest symbol value detected. + * @return : count of the most frequent symbol (which isn't identified). + * or an error code, which can be tested using HIST_isError(). + * note : if return == srcSize, there is only one symbol. + */ +size_t HIST_count(unsigned* count, unsigned* maxSymbolValuePtr, + const void* src, size_t srcSize); + +unsigned HIST_isError(size_t code); /**< tells if a return value is an error code */ + + +/* --- advanced histogram functions --- */ + +#define HIST_WKSP_SIZE_U32 1024 +#define HIST_WKSP_SIZE (HIST_WKSP_SIZE_U32 * sizeof(unsigned)) +/** HIST_count_wksp() : + * Same as HIST_count(), but using an externally provided scratch buffer. + * Benefit is this function will use very little stack space. + * `workSpace` is a writable buffer which must be 4-bytes aligned, + * `workSpaceSize` must be >= HIST_WKSP_SIZE + */ +size_t HIST_count_wksp(unsigned* count, unsigned* maxSymbolValuePtr, + const void* src, size_t srcSize, + void* workSpace, size_t workSpaceSize); + +/** HIST_countFast() : + * same as HIST_count(), but blindly trusts that all byte values within src are <= *maxSymbolValuePtr. + * This function is unsafe, and will segfault if any value within `src` is `> *maxSymbolValuePtr` + */ +size_t HIST_countFast(unsigned* count, unsigned* maxSymbolValuePtr, + const void* src, size_t srcSize); + +/** HIST_countFast_wksp() : + * Same as HIST_countFast(), but using an externally provided scratch buffer. + * `workSpace` is a writable buffer which must be 4-bytes aligned, + * `workSpaceSize` must be >= HIST_WKSP_SIZE + */ +size_t HIST_countFast_wksp(unsigned* count, unsigned* maxSymbolValuePtr, + const void* src, size_t srcSize, + void* workSpace, size_t workSpaceSize); + +/*! HIST_count_simple() : + * Same as HIST_countFast(), this function is unsafe, + * and will segfault if any value within `src` is `> *maxSymbolValuePtr`. + * It is also a bit slower for large inputs. + * However, it does not need any additional memory (not even on stack). + * @return : count of the most frequent symbol. + * Note this function doesn't produce any error (i.e. it must succeed). + */ +unsigned HIST_count_simple(unsigned* count, unsigned* maxSymbolValuePtr, + const void* src, size_t srcSize); + +/*! HIST_add() : + * Lowest level: just add nb of occurrences of characters from @src into @count. + * @count is not reset. @count array is presumed large enough (i.e. 1 KB). + @ This function does not need any additional stack memory. + */ +void HIST_add(unsigned* count, const void* src, size_t srcSize); diff --git a/src/huff/huf.h b/src/huff/huf.h new file mode 100644 index 000000000..fd1aa29c6 --- /dev/null +++ b/src/huff/huf.h @@ -0,0 +1,275 @@ +/* ****************************************************************** + * huff0 huffman codec, + * part of Finite State Entropy library + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * You can contact the author at : + * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. +****************************************************************** */ + +#ifndef HUF_H_298734234 +#define HUF_H_298734234 + +/* *** Dependencies *** */ +#include /* size_t */ +#include "mem.h" /* U32 */ + +/* *** Tool functions *** */ +#define HUF_BLOCKSIZE_MAX (128 * 1024) /**< maximum input size for a single block compressed with HUF_compress */ +size_t HUF_compressBound(size_t size); /**< maximum compressed size (worst case) */ + +/* Error Management */ +unsigned HUF_isError(size_t code); /**< tells if a return value is an error code */ +const char* HUF_getErrorName(size_t code); /**< provides error code string (useful for debugging) */ + + +#define HUF_WORKSPACE_SIZE ((8 << 10) + 512 /* sorting scratch space */) +#define HUF_WORKSPACE_SIZE_U64 (HUF_WORKSPACE_SIZE / sizeof(U64)) + +/* *** Constants *** */ +#define HUF_TABLELOG_MAX 12 /* max runtime value of tableLog (due to static allocation); can be modified up to HUF_TABLELOG_ABSOLUTEMAX */ +#define HUF_TABLELOG_DEFAULT 11 /* default tableLog value when none specified */ +#define HUF_SYMBOLVALUE_MAX 255 + +#define HUF_TABLELOG_ABSOLUTEMAX 12 /* absolute limit of HUF_MAX_TABLELOG. Beyond that value, code does not work */ +#if (HUF_TABLELOG_MAX > HUF_TABLELOG_ABSOLUTEMAX) +# error "HUF_TABLELOG_MAX is too large !" +#endif + + +/* **************************************** +* Static allocation +******************************************/ +/* HUF buffer bounds */ +#define HUF_CTABLEBOUND 129 +#define HUF_BLOCKBOUND(size) (size + (size>>8) + 8) /* only true when incompressible is pre-filtered with fast heuristic */ +#define HUF_COMPRESSBOUND(size) (HUF_CTABLEBOUND + HUF_BLOCKBOUND(size)) /* Macro version, useful for static allocation */ + +/* static allocation of HUF's Compression Table */ +/* this is a private definition, just exposed for allocation and strict aliasing purpose. never EVER access its members directly */ +typedef size_t HUF_CElt; /* consider it an incomplete type */ +#define HUF_CTABLE_SIZE_ST(maxSymbolValue) ((maxSymbolValue)+2) /* Use tables of size_t, for proper alignment */ +#define HUF_CTABLE_SIZE(maxSymbolValue) (HUF_CTABLE_SIZE_ST(maxSymbolValue) * sizeof(size_t)) +#define HUF_CREATE_STATIC_CTABLE(name, maxSymbolValue) \ + HUF_CElt name[HUF_CTABLE_SIZE_ST(maxSymbolValue)] /* no final ; */ + +/* static allocation of HUF's DTable */ +typedef U32 HUF_DTable; +#define HUF_DTABLE_SIZE(maxTableLog) (1 + (1<<(maxTableLog))) +#define HUF_CREATE_STATIC_DTABLEX1(DTable, maxTableLog) \ + HUF_DTable DTable[HUF_DTABLE_SIZE((maxTableLog)-1)] = { ((U32)((maxTableLog)-1) * 0x01000001) } +#define HUF_CREATE_STATIC_DTABLEX2(DTable, maxTableLog) \ + HUF_DTable DTable[HUF_DTABLE_SIZE(maxTableLog)] = { ((U32)(maxTableLog) * 0x01000001) } + + +/* **************************************** +* Advanced decompression functions +******************************************/ + +/** + * Huffman flags bitset. + * For all flags, 0 is the default value. + */ +typedef enum { + /** + * If compiled with DYNAMIC_BMI2: Set flag only if the CPU supports BMI2 at runtime. + * Otherwise: Ignored. + */ + HUF_flags_bmi2 = (1 << 0), + /** + * If set: Test possible table depths to find the one that produces the smallest header + encoded size. + * If unset: Use heuristic to find the table depth. + */ + HUF_flags_optimalDepth = (1 << 1), + /** + * If set: If the previous table can encode the input, always reuse the previous table. + * If unset: If the previous table can encode the input, reuse the previous table if it results in a smaller output. + */ + HUF_flags_preferRepeat = (1 << 2), + /** + * If set: Sample the input and check if the sample is uncompressible, if it is then don't attempt to compress. + * If unset: Always histogram the entire input. + */ + HUF_flags_suspectUncompressible = (1 << 3), + /** + * If set: Don't use assembly implementations + * If unset: Allow using assembly implementations + */ + HUF_flags_disableAsm = (1 << 4), + /** + * If set: Don't use the fast decoding loop, always use the fallback decoding loop. + * If unset: Use the fast decoding loop when possible. + */ + HUF_flags_disableFast = (1 << 5) +} HUF_flags_e; + + +/* **************************************** + * HUF detailed API + * ****************************************/ +#define HUF_OPTIMAL_DEPTH_THRESHOLD ZSTD_btultra + +/*! HUF_compress() does the following: + * 1. count symbol occurrence from source[] into table count[] using FSE_count() (exposed within "fse.h") + * 2. (optional) refine tableLog using HUF_optimalTableLog() + * 3. build Huffman table from count using HUF_buildCTable() + * 4. save Huffman table to memory buffer using HUF_writeCTable() + * 5. encode the data stream using HUF_compress4X_usingCTable() + * + * The following API allows targeting specific sub-functions for advanced tasks. + * For example, it's possible to compress several blocks using the same 'CTable', + * or to save and regenerate 'CTable' using external methods. + */ +unsigned HUF_minTableLog(unsigned symbolCardinality); +unsigned HUF_cardinality(const unsigned* count, unsigned maxSymbolValue); +unsigned HUF_optimalTableLog(unsigned maxTableLog, size_t srcSize, unsigned maxSymbolValue, void* workSpace, + size_t wkspSize, HUF_CElt* table, const unsigned* count, int flags); /* table is used as scratch space for building and testing tables, not a return value */ +size_t HUF_writeCTable_wksp(void* dst, size_t maxDstSize, const HUF_CElt* CTable, unsigned maxSymbolValue, unsigned huffLog, void* workspace, size_t workspaceSize); +size_t HUF_compress4X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); +size_t HUF_estimateCompressedSize(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); +int HUF_validateCTable(const HUF_CElt* CTable, const unsigned* count, unsigned maxSymbolValue); + +typedef enum { + HUF_repeat_none, /**< Cannot use the previous table */ + HUF_repeat_check, /**< Can use the previous table but it must be checked. Note : The previous table must have been constructed by HUF_compress{1, 4}X_repeat */ + HUF_repeat_valid /**< Can use the previous table and it is assumed to be valid */ + } HUF_repeat; + +/** HUF_compress4X_repeat() : + * Same as HUF_compress4X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. + * If it uses hufTable it does not modify hufTable or repeat. + * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used. + * If preferRepeat then the old table will always be used if valid. + * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */ +size_t HUF_compress4X_repeat(void* dst, size_t dstSize, + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned tableLog, + void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ + HUF_CElt* hufTable, HUF_repeat* repeat, int flags); + +/** HUF_buildCTable_wksp() : + * Same as HUF_buildCTable(), but using externally allocated scratch buffer. + * `workSpace` must be aligned on 4-bytes boundaries, and its size must be >= HUF_CTABLE_WORKSPACE_SIZE. + */ +#define HUF_CTABLE_WORKSPACE_SIZE_U32 ((4 * (HUF_SYMBOLVALUE_MAX + 1)) + 192) +#define HUF_CTABLE_WORKSPACE_SIZE (HUF_CTABLE_WORKSPACE_SIZE_U32 * sizeof(unsigned)) +size_t HUF_buildCTable_wksp (HUF_CElt* tree, + const unsigned* count, U32 maxSymbolValue, U32 maxNbBits, + void* workSpace, size_t wkspSize); + +/*! HUF_readStats() : + * Read compact Huffman tree, saved by HUF_writeCTable(). + * `huffWeight` is destination buffer. + * @return : size read from `src` , or an error Code . + * Note : Needed by HUF_readCTable() and HUF_readDTableXn() . */ +size_t HUF_readStats(BYTE* huffWeight, size_t hwSize, + U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, + const void* src, size_t srcSize); + +/*! HUF_readStats_wksp() : + * Same as HUF_readStats() but takes an external workspace which must be + * 4-byte aligned and its size must be >= HUF_READ_STATS_WORKSPACE_SIZE. + * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. + */ +#define HUF_READ_STATS_WORKSPACE_SIZE_U32 FSE_DECOMPRESS_WKSP_SIZE_U32(6, HUF_TABLELOG_MAX-1) +#define HUF_READ_STATS_WORKSPACE_SIZE (HUF_READ_STATS_WORKSPACE_SIZE_U32 * sizeof(unsigned)) +size_t HUF_readStats_wksp(BYTE* huffWeight, size_t hwSize, + U32* rankStats, U32* nbSymbolsPtr, U32* tableLogPtr, + const void* src, size_t srcSize, + void* workspace, size_t wkspSize, + int flags); + +/** HUF_readCTable() : + * Loading a CTable saved with HUF_writeCTable() */ +size_t HUF_readCTable (HUF_CElt* CTable, unsigned* maxSymbolValuePtr, const void* src, size_t srcSize, unsigned *hasZeroWeights); + +/** HUF_getNbBitsFromCTable() : + * Read nbBits from CTable symbolTable, for symbol `symbolValue` presumed <= HUF_SYMBOLVALUE_MAX + * Note 1 : If symbolValue > HUF_readCTableHeader(symbolTable).maxSymbolValue, returns 0 + * Note 2 : is not inlined, as HUF_CElt definition is private + */ +U32 HUF_getNbBitsFromCTable(const HUF_CElt* symbolTable, U32 symbolValue); + +typedef struct { + BYTE tableLog; + BYTE maxSymbolValue; + BYTE unused[sizeof(size_t) - 2]; +} HUF_CTableHeader; + +/** HUF_readCTableHeader() : + * @returns The header from the CTable specifying the tableLog and the maxSymbolValue. + */ +HUF_CTableHeader HUF_readCTableHeader(HUF_CElt const* ctable); + +/* + * HUF_decompress() does the following: + * 1. select the decompression algorithm (X1, X2) based on pre-computed heuristics + * 2. build Huffman table from save, using HUF_readDTableX?() + * 3. decode 1 or 4 segments in parallel using HUF_decompress?X?_usingDTable() + */ + +/** HUF_selectDecoder() : + * Tells which decoder is likely to decode faster, + * based on a set of pre-computed metrics. + * @return : 0==HUF_decompress4X1, 1==HUF_decompress4X2 . + * Assumption : 0 < dstSize <= 128 KB */ +U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize); + +/** + * The minimum workspace size for the `workSpace` used in + * HUF_readDTableX1_wksp() and HUF_readDTableX2_wksp(). + * + * The space used depends on HUF_TABLELOG_MAX, ranging from ~1500 bytes when + * HUF_TABLE_LOG_MAX=12 to ~1850 bytes when HUF_TABLE_LOG_MAX=15. + * Buffer overflow errors may potentially occur if code modifications result in + * a required workspace size greater than that specified in the following + * macro. + */ +#define HUF_DECOMPRESS_WORKSPACE_SIZE ((2 << 10) + (1 << 9)) +#define HUF_DECOMPRESS_WORKSPACE_SIZE_U32 (HUF_DECOMPRESS_WORKSPACE_SIZE / sizeof(U32)) + + +/* ====================== */ +/* single stream variants */ +/* ====================== */ + +size_t HUF_compress1X_usingCTable(void* dst, size_t dstSize, const void* src, size_t srcSize, const HUF_CElt* CTable, int flags); +/** HUF_compress1X_repeat() : + * Same as HUF_compress1X_wksp(), but considers using hufTable if *repeat != HUF_repeat_none. + * If it uses hufTable it does not modify hufTable or repeat. + * If it doesn't, it sets *repeat = HUF_repeat_none, and it sets hufTable to the table used. + * If preferRepeat then the old table will always be used if valid. + * If suspectUncompressible then some sampling checks will be run to potentially skip huffman coding */ +size_t HUF_compress1X_repeat(void* dst, size_t dstSize, + const void* src, size_t srcSize, + unsigned maxSymbolValue, unsigned tableLog, + void* workSpace, size_t wkspSize, /**< `workSpace` must be aligned on 4-bytes boundaries, `wkspSize` must be >= HUF_WORKSPACE_SIZE */ + HUF_CElt* hufTable, HUF_repeat* repeat, int flags); + +size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); +#ifndef HUF_FORCE_DECOMPRESS_X1 +size_t HUF_decompress1X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); /**< double-symbols decoder */ +#endif + +/* BMI2 variants. + * If the CPU has BMI2 support, pass bmi2=1, otherwise pass bmi2=0. + */ +size_t HUF_decompress1X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); +#ifndef HUF_FORCE_DECOMPRESS_X2 +size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); +#endif +size_t HUF_decompress4X_usingDTable(void* dst, size_t maxDstSize, const void* cSrc, size_t cSrcSize, const HUF_DTable* DTable, int flags); +size_t HUF_decompress4X_hufOnly_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, void* workSpace, size_t wkspSize, int flags); +#ifndef HUF_FORCE_DECOMPRESS_X2 +size_t HUF_readDTableX1_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); +#endif +#ifndef HUF_FORCE_DECOMPRESS_X1 +size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, void* workSpace, size_t wkspSize, int flags); +#endif + +#endif /* HUF_H_298734234 */ diff --git a/src/huff/mem.h b/src/huff/mem.h new file mode 100644 index 000000000..cbd52ec5f --- /dev/null +++ b/src/huff/mem.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under both the BSD-style license (found in the + * LICENSE file in the root directory of this source tree) and the GPLv2 (found + * in the COPYING file in the root directory of this source tree). + * You may select, at your option, one of the above-listed licenses. + */ + +#ifndef MEM_H_MODULE +#define MEM_H_MODULE + +/*-**************************************** +* Dependencies +******************************************/ +#include /* size_t, ptrdiff_t */ +#include /* intptr_t */ +#define MEM_STATIC + +typedef uint32_t U32; +typedef uint8_t BYTE; + +#endif /* MEM_H_MODULE */ diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 6699a6b44..53480b472 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -77,8 +77,6 @@ if (WITH_ASAN OR WITH_USAN) target_compile_definitions(dfly_transaction PRIVATE SANITIZERS) endif() -find_library(ZSTD_LIB NAMES libzstd.a libzstdstatic.a zstd NAMES_PER_DIR REQUIRED) - if (WITH_AWS) SET(AWS_LIB awsv2_lib) add_definitions(-DWITH_AWS) @@ -87,7 +85,7 @@ endif() cxx_link(dfly_transaction dfly_core strings_lib TRDP::fast_float) cxx_link(dragonfly_lib dfly_transaction dfly_facade redis_lib ${AWS_LIB} jsonpath strings_lib html_lib gcp_lib - http_client_lib absl::random_random TRDP::jsoncons ${ZSTD_LIB} TRDP::lz4 + http_client_lib absl::random_random TRDP::jsoncons TRDP::zstd TRDP::lz4 TRDP::croncpp TRDP::flatbuffers) if (DF_USE_SSL) diff --git a/src/server/debugcmd.cc b/src/server/debugcmd.cc index aa2b6b9c1..ed4377363 100644 --- a/src/server/debugcmd.cc +++ b/src/server/debugcmd.cc @@ -3,7 +3,11 @@ // #include "server/debugcmd.h" +#define HUF_STATIC_LINKING_ONLY + extern "C" { +#include "huff/hist.h" +#include "huff/huf.h" #include "redis/redis_aux.h" } @@ -11,6 +15,8 @@ extern "C" { #include #include #include +#include +#include #include #include @@ -312,6 +318,64 @@ void DoBuildObjHist(EngineShard* shard, ConnectionContext* cntx, ObjHistMap* obj } } +void FillDictSamples(PrimeTable* table, string* sample_buf) { + PrimeTable::Cursor cursor; + unsigned steps = 0; + string scratch; + constexpr size_t kMaxLen = 512; + do { + cursor = table->Traverse(cursor, [&](PrimeIterator it) { + it->first.GetString(&scratch); + size_t len = std::min(scratch.size(), kMaxLen); + sample_buf->append(scratch.data(), len); + }); + + if (sample_buf->size() > 1_MB) + return; + + if (steps >= 20000) { + steps = 0; + ThisFiber::Yield(); + } + } while (cursor); +} + +struct HufHist { + static constexpr unsigned kMaxSymbol = 255; + unsigned hist[kMaxSymbol + 1]; // histogram of symbols. + unsigned max_symbol = 0; // what is the max symbol of the histogram. + + HufHist() { + memset(hist, 0, sizeof(hist)); + } + + void Merge(const HufHist& other) { + max_symbol = std::max(max_symbol, other.max_symbol); + for (unsigned i = 0; i <= max_symbol; ++i) { + hist[i] += other.hist[i]; + } + } +}; + +void DoComputeHist(EngineShard* shard, ConnectionContext* cntx, HufHist* dest) { + auto& db_slice = cntx->ns->GetDbSlice(shard->shard_id()); + DbTable* dbt = db_slice.GetDBTable(cntx->db_index()); + CHECK(dbt); + + string sample_buf; + FillDictSamples(&dbt->prime, &sample_buf); + + if (sample_buf.empty()) { + return; + } + dest->max_symbol = 255; + + unique_ptr wksp(new uint32_t[HIST_WKSP_SIZE_U32]); + size_t max_freq = HIST_count_wksp(dest->hist, &dest->max_symbol, sample_buf.data(), + sample_buf.size(), wksp.get(), HIST_WKSP_SIZE); + CHECK(!HIST_isError(max_freq)); +} + ObjInfo InspectOp(ConnectionContext* cntx, string_view key) { auto& db_slice = cntx->ns->GetCurrentDbSlice(); auto db_index = cntx->db_index(); @@ -514,6 +578,8 @@ void DebugCmd::Run(CmdArgList args, facade::SinkReplyBuilder* builder) { " traffic logging is stopped.", "RECVSIZE [ | ENABLE | DISABLE]", " Prints the histogram of the received request sizes on the given thread", + "COMPRESSION" + " Estimate the compressability of values of the given type", "HELP", " Prints this help.", }; @@ -585,6 +651,10 @@ void DebugCmd::Run(CmdArgList args, facade::SinkReplyBuilder* builder) { return Keys(args.subspan(1), builder); } + if (subcmd == "COMPRESSION") { + return Compression(builder); + } + string reply = UnknownSubCmd(subcmd, "DEBUG"); return builder->SendError(reply, kSyntaxErrType); } @@ -1209,4 +1279,47 @@ void DebugCmd::Keys(CmdArgList args, facade::SinkReplyBuilder* builder) { return builder->SendError(kSyntaxErr); } +void DebugCmd::Compression(facade::SinkReplyBuilder* builder) { + auto* rb = static_cast(builder); + + fb2::Mutex mu; + HufHist hist; + shard_set->RunBlockingInParallel([&](EngineShard* shard) { + HufHist local; + DoComputeHist(shard, cntx_, &local); + std::unique_lock lk(mu); + hist.Merge(local); + }); + + HUF_CREATE_STATIC_CTABLE(huf_ctable, HufHist::kMaxSymbol); + + size_t num_bits = 0, compressed_size = 0, raw_size = 0; + + if (hist.max_symbol) { + unique_ptr wrkspace(new uint32_t[HUF_CTABLE_WORKSPACE_SIZE_U32]); + constexpr size_t kWspSize = HUF_CTABLE_WORKSPACE_SIZE; + num_bits = + HUF_buildCTable_wksp(huf_ctable, hist.hist, hist.max_symbol, 0, wrkspace.get(), kWspSize); + + compressed_size = HUF_estimateCompressedSize(huf_ctable, hist.hist, hist.max_symbol); + raw_size = 0; + for (unsigned i = 0; i < hist.max_symbol; i++) { + raw_size += hist.hist[i]; + } + } + + rb->StartCollection(5, RedisReplyBuilder::CollectionType::MAP); + rb->SendSimpleString("max_symbol"); + rb->SendLong(hist.max_symbol); + rb->SendSimpleString("max_bits"); + rb->SendLong(num_bits); + rb->SendSimpleString("raw_size"); + rb->SendLong(raw_size); + rb->SendSimpleString("compressed_size"); + rb->SendLong(compressed_size); + rb->SendSimpleString("ratio"); + double ratio = raw_size > 0 ? static_cast(compressed_size) / raw_size : 0; + rb->SendDouble(ratio); +} + } // namespace dfly diff --git a/src/server/debugcmd.h b/src/server/debugcmd.h index fab9c37d0..e319cd39f 100644 --- a/src/server/debugcmd.h +++ b/src/server/debugcmd.h @@ -57,6 +57,7 @@ class DebugCmd { void RecvSize(std::string_view param, facade::SinkReplyBuilder* builder); void Topk(CmdArgList args, facade::SinkReplyBuilder* builder); void Keys(CmdArgList args, facade::SinkReplyBuilder* builder); + void Compression(facade::SinkReplyBuilder* builder); ServerFamily& sf_; cluster::ClusterFamily& cf_;