author | Olivier Brunel
<jjk@jjacky.com> 2023-02-15 18:05:58 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2023-02-15 18:05:58 UTC |
parent | a80d223784d8a210362816ff0af2b0b9901cf8bb |
.gitignore | +2 | -0 |
doc/nextsplit_ae.3.md | +51 | -0 |
include/limb/int.h | +5 | -0 |
include/limb/nextsplit.h | +11 | -0 |
meta/AUTHORS | +5 | -0 |
meta/bins/mkrabintables | +4 | -0 |
meta/libs/limb | +4 | -0 |
project.mk | +9 | -0 |
src/nextsplit_ae.c | +55 | -0 |
src/nextsplit_buz.c | +100 | -0 |
src/nextsplit_rabin.c | +60 | -0 |
src/tools/mkrabintables.c | +110 | -0 |
diff --git a/.gitignore b/.gitignore index fe2f03b..827b9aa 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,11 @@ /common.mk /config.mk /include/config.h +/include/limb/rabin-tables.h *.o *.lo *.d liblimb.a liblimb.so +/mkrabintables /skalibs diff --git a/doc/nextsplit_ae.3.md b/doc/nextsplit_ae.3.md new file mode 100644 index 0000000..29a4f01 --- /dev/null +++ b/doc/nextsplit_ae.3.md @@ -0,0 +1,51 @@ +% limb manual +% nextsplit_ae(3) + +# NAME + +nextsplit_ae, nextsplit_buz, nextsplit_rabin - find the next split for +variable-length chunks in a data stream + + +# SYNOPSIS + + #include <limb/nextsplit.h> + +```pre hl +size_t nextsplit_ae(size_t <em>min</em>, size_t <em>avg</em>, + const void *<em>data</em>, size_t <em>dlen</em>) +size_t nextsplit_buz(size_t <em>min</em>, size_t <em>avg</em>, + const void *<em>data</em>, size_t <em>dlen</em>) +size_t nextsplit_rabin(size_t <em>min</em>, size_t <em>avg</em>, + const void *<em>data</em>, size_t <em>dlen</em>) +``` + +# DESCRIPTION + +These functions are used for content-based chunking, in order to find the next +breakpoint where to split the data stream into a chunk - useful for things such +as data deduplication. + +Each of them will look into `data` (up to `dlen`) for position to split a chunk, +which shall be at least `min` bytes long and with average/targeted length of +`avg`. Failing to find one, they will return `dlen` for a full chunk - as such, +`dlen` should be the maximum chunk size and not the total data length. + +The `nextsplit_ae`() function uses Asymmetric Extremum Content Defined Chunking +Algorithm, which is notably extremely fast. + +The `nextsplit_buz`() function uses the buzhash rolling hash, which gets quite +better deduplication results whilst remaining very fast (though not as fast as +AE). + +The `nextsplit_rabin`() function uses a rolling-hash algorithm based on Rabin +fingerprint, which tend to get slightly better deduplication results, albeit +being slower. + +Note that it isn't uncommon for `nextsplit_buz`() to lead to better +deduplication results (than both other alternatives). + +# RETURN VALUES + +All those functions will return the length of the next chunk from `data` as +determined by their algorithm, or `dlen` when no such split could be determined. diff --git a/include/limb/int.h b/include/limb/int.h index df755ab..c730d6b 100644 --- a/include/limb/int.h +++ b/include/limb/int.h @@ -10,6 +10,11 @@ typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; +#define U8_C(u) UINT8_C(u) +#define U16_C(u) UINT16_C(u) +#define U32_C(u) UINT32_C(u) +#define U64_C(u) UINT64_C(u) + extern int msb64(u64 val); #define u64_pack(val,dst) uint64_pack((char *) (dst), val) diff --git a/include/limb/nextsplit.h b/include/limb/nextsplit.h new file mode 100644 index 0000000..4495901 --- /dev/null +++ b/include/limb/nextsplit.h @@ -0,0 +1,11 @@ +#ifndef LIMB_NEXTSPLIT_H +#define LIMB_NEXTSPLIT_H + +#include <string.h> /* size_t */ +#include "limb/int.h" + +size_t nextsplit_ae(size_t min, size_t avg, const void *data, size_t dlen); +size_t nextsplit_buz(size_t min, size_t avg, const void *data, size_t dlen); +size_t nextsplit_rabin(size_t min, size_t avg, const void *data, size_t dlen); + +#endif /* LIMB_NEXTSPLIT_H */ diff --git a/meta/AUTHORS b/meta/AUTHORS index 10768e8..7f8bd96 100644 --- a/meta/AUTHORS +++ b/meta/AUTHORS @@ -5,3 +5,8 @@ Contributors: * Aleksey Kravchenko <rhash.admin@gmail.com> [sha3] * Samuel Neves [blake3] * Jack O'Connor [blake3] +* Jonas Borgström <jonas@borgstrom.se> & The Borg Collective [nextsplit_buz] +* Yucheng Zhang [nextsplit_ae] + +Thanks to: +* Min Fu diff --git a/meta/bins/mkrabintables b/meta/bins/mkrabintables new file mode 100644 index 0000000..6e3e1ca --- /dev/null +++ b/meta/bins/mkrabintables @@ -0,0 +1,4 @@ +obj/tools/mkrabintables.o +obj/msb64.o +obj/put.o +skalibs diff --git a/meta/libs/limb b/meta/libs/limb index 6e979ab..71f9d43 100644 --- a/meta/libs/limb +++ b/meta/libs/limb @@ -10,6 +10,10 @@ obj/msb64.o # {,un}pack u64 obj/uint64_pack_trim.o obj/uint64_unpack_trim.o +# content-based chunking +obj/nextsplit_ae.o +obj/nextsplit_buz.o +obj/nextsplit_rabin.o # SHA3 obj/sha3/byte_order.o obj/sha3/rhash_sha3_process_block.o diff --git a/project.mk b/project.mk index d20f193..7ed309a 100644 --- a/project.mk +++ b/project.mk @@ -1,5 +1,14 @@ LIBS = limb +TOOLS = mkrabintables + +CLEAN += include/limb/rabin-tables.h + +obj/nextsplit_rabin.o obj/nextsplit_rabin.lo: include/limb/rabin-tables.h + +include/limb/rabin-tables.h: mkrabintables + $(_GEN) ./mkrabintables > $@ + ifeq ($(BITS),64) BLAKE3_OPTIMIZ := obj/blake3/blake3_avx2_x86-64_unix.o \ obj/blake3/blake3_avx512_x86-64_unix.o \ diff --git a/src/nextsplit_ae.c b/src/nextsplit_ae.c new file mode 100644 index 0000000..166c031 --- /dev/null +++ b/src/nextsplit_ae.c @@ -0,0 +1,55 @@ +#include <endian.h> +#include "limb/nextsplit.h" + +/* Author: Yucheng Zhang + * For more see paper: AE: An Asymmetric Extremum Content Defined Chunking + * Algorithm for Fast and Bandwidth-Efficient Data Deduplication + */ + +#if __BYTE_ORDER == __BIG_ENDIAN +# define GETLE32(u) (u) +#else +# define GETLE32(u) __builtin_bswap32(u) +#endif + +static inline int +cmp(const u8 *x, const u8 *y) +{ + int ret; + u32 a = GETLE32(* ((u32 *) x)); + u32 b = GETLE32(* ((u32 *) y)); + if (a > b) + ret = 1; + else + ret = -1; + return ret; +} + +size_t +nextsplit_ae(size_t min, size_t avg, const void *data, size_t dlen) +{ + if (dlen <= min) return dlen; + + double e = 2.718281828; + int window_size = avg / (e - 1); + /* cur points to the current position + * max points to the position of max value + * end points to the end of buffer + */ + const u8 *cur = (const u8 *) data + 1; + const u8 *max = (const u8 *) data; + const u8 *end = (const u8 *) data + dlen - sizeof(u32); + + if (dlen <= window_size + sizeof(u32)) + return dlen; + + for ( ; cur <= end; ++cur) { + if (cmp(cur, max) < 0) { + max = cur; + continue; + } + if (cur == max + window_size) + return cur - (const u8 *) data; + } + return dlen; +} diff --git a/src/nextsplit_buz.c b/src/nextsplit_buz.c new file mode 100644 index 0000000..6595741 --- /dev/null +++ b/src/nextsplit_buz.c @@ -0,0 +1,100 @@ +/* Original code from BorgBackup: + * Copyright (C) 2015-2022 The Borg Collective + * Copyright (C) 2010-2014 Jonas Borgström <jonas@borgstrom.se> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. The name of the author may not 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 + * OWNER 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. + */ +#include "limb/nextsplit.h" + +size_t +nextsplit_buz(size_t min, size_t avg, const void *data, size_t dlen) +{ + u32 T[] = + { + 0XE7F831EC, 0XF4026465, 0XAFB50CAE, 0X6D553C7A, 0XD639EFE3, 0X19A7B895, 0X9ABA5B21, 0X5417D6D4, + 0X35FD2B84, 0XD1F6A159, 0X3F8E323F, 0XB419551C, 0XF444CEBF, 0X21DC3B80, 0XDE8D1E36, 0X84A32436, + 0XBEB35A9D, 0XA36F24AA, 0XA4E60186, 0X98D18FFE, 0X3F042F9E, 0XDB228BCD, 0X096474B7, 0X5C20C2F7, + 0XF9EEC872, 0XE8625275, 0XB9D38F80, 0XD48EB716, 0X22A950B4, 0X3CBAAEAA, 0XC37CDDD3, 0X8FEA6F6A, + 0X1D55D526, 0X7FD6D3B3, 0XDAA072EE, 0X4345AC40, 0XA077C642, 0X8F2BD45B, 0X28509110, 0X55557613, + 0XFFC17311, 0XD961FFEF, 0XE532C287, 0XAAB95937, 0X46D38365, 0XB065C703, 0XF2D91D0F, 0X92CD4BB0, + 0X4007C712, 0XF35509DD, 0X505B2F69, 0X557EAD81, 0X310F4563, 0XBDDC5BE8, 0X9760F38C, 0X701E0205, + 0X00157244, 0X14912826, 0XDC4CA32B, 0X67B196DE, 0X5DB292E8, 0X8C1B406B, 0X01F34075, 0XFA2520F7, + 0X73BC37AB, 0X1E18BC30, 0XFE2C6CB3, 0X20C522D0, 0X5639E3DB, 0X942BDA35, 0X899AF9D1, 0XCED44035, + 0X98CC025B, 0X255F5771, 0X70FEFA24, 0XE928FA4D, 0X2C030405, 0XB9325590, 0X20CB63BD, 0XA166305D, + 0X80E52C0A, 0XA8FAFE2F, 0X1AD13F7D, 0XCFAF3685, 0X6C83A199, 0X7D26718A, 0XDE5DFCD9, 0X79CF7355, + 0X8979D7FB, 0XEBF8C55E, 0XEBE408E4, 0XCD2AFFBA, 0XE483BE6E, 0XE239D6DE, 0X5DC1E9E0, 0X0473931F, + 0X851B097C, 0XAC5DB249, 0X09C0F9F2, 0XD8D2F134, 0XE6F38E41, 0XB1C71BF1, 0X52B6E4DB, 0X07224424, + 0X6CF73E85, 0X4F25D89C, 0X782A7D74, 0X10A68DCD, 0X3A868189, 0XD570D2DC, 0X69630745, 0X9542ED86, + 0X331CD6B2, 0XA84B5B28, 0X07879C9D, 0X38372F64, 0X7185DB11, 0X25BA7C83, 0X01061523, 0XE6792F9F, + 0XE5DF07D1, 0X4321B47F, 0X7D2469D8, 0X1A3A4F90, 0X48BE29A3, 0X669071AF, 0X8EC8DD31, 0X0810BFBF, + 0X813A06B4, 0X68538345, 0X65865DDC, 0X43A71B8E, 0X78619A56, 0X5A34451D, 0X5BDAA3ED, 0X71EDC7E9, + 0X17AC9A20, 0X78D10BFA, 0X6C1E7F35, 0XD51839D9, 0X240CBC51, 0X33513CC1, 0XD2B4F795, 0XCCAA8186, + 0X0BABE682, 0XA33CF164, 0X18C643EA, 0XC1CA105F, 0X9959147A, 0X6D3D94DE, 0X0B654FBE, 0XED902CA0, + 0X7D835CB5, 0X99BA1509, 0X6445C922, 0X495E76C2, 0XF07194BC, 0XA1631D7E, 0X677076A5, 0X89FFFE35, + 0X1A49BCF3, 0X8E6C948A, 0X0144C917, 0X8D93AEA1, 0X16F87DDF, 0XC8F25D49, 0X1FB11297, 0X27E750CD, + 0X2F422DA1, 0XDEE89A77, 0X1534C643, 0X457B7B8B, 0XAF172F7A, 0X6B9B09D6, 0X33573F7F, 0XF14E15C4, + 0X526467D5, 0XAF488241, 0X87C3EE0D, 0X33BE490C, 0X95AA6E52, 0X43EC242E, 0XD77DE99B, 0XD018334F, + 0X5B78D407, 0X498EB66B, 0XB1279FA8, 0XB38B0EA6, 0X90718376, 0XE325DEE2, 0X8E2F2CBA, 0XCAA5BDEC, + 0X9D652C56, 0XAD68F5CB, 0XA77591AF, 0X88E37EE8, 0XF8FAA221, 0XFCBBBE47, 0X4F407786, 0XAF393889, + 0XF444A1D9, 0X15AE1A2F, 0X40AA7097, 0X6F9486AC, 0X29D232A3, 0XE47609E9, 0XE8B631FF, 0XBA8565F4, + 0X11288749, 0X46C9A838, 0XEB1B7CD8, 0XF516BBB1, 0XFB74FDA0, 0X010996E6, 0X4C994653, 0X1D889512, + 0X53DCD9A3, 0XDD074697, 0X1E78E17C, 0X637C98BF, 0X930BB219, 0XCF7F75B0, 0XCB9355FB, 0X9E623009, + 0XE466D82C, 0X28F968D3, 0XFEB385D9, 0X238E026C, 0XB8ED0560, 0X0C6A027A, 0X3D6FEC4B, 0XBB4B2EC2, + 0XE715031C, 0XEDED011D, 0XCDC4D3B9, 0XC456FC96, 0XDD0EEA20, 0XB3DF8EC9, 0X12351993, 0XD9CBB01C, + 0X603147A2, 0XCF37D17D, 0XF7FCD9DC, 0XD8556FA3, 0X104C8131, 0X13152774, 0XB4715811, 0X6A72C2C9, + 0XC5AE37BB, 0XA76CE12A, 0X8150D8F3, 0X2EC29218, 0XA35F0984, 0X48C0647E, 0X0B5FF98C, 0X71893F7B + }; + u32 mask = (1 << (msb64(avg) - 1)) - 1; + +#define WINDOW_SIZE 2047 +#define BARREL_SHIFT(v, shift) ( ((v) << (shift)) | ((v) >> ((32 - (shift)) & 0x1F)) ) + + const u8 *d = data; + u32 i, sum, imod; + + if (dlen <= min + WINDOW_SIZE) return dlen; + + sum = 0; + for (i = WINDOW_SIZE - 1; i > 0; --i) { + imod = i & 0x1F; + sum ^= BARREL_SHIFT(T[*d], imod); + ++d; + } + sum ^= T[*d]; + + d = data; + for (i = WINDOW_SIZE; i < dlen - WINDOW_SIZE; ++i) { + sum = BARREL_SHIFT(sum, 1) ^ BARREL_SHIFT(T[*d], 0x1F) ^ T[d[WINDOW_SIZE]]; + if ((sum & mask) == 0) break; + ++d; + } + if (i >= dlen - WINDOW_SIZE) + i = dlen; + + return i; +} diff --git a/src/nextsplit_rabin.c b/src/nextsplit_rabin.c new file mode 100644 index 0000000..99c7c8c --- /dev/null +++ b/src/nextsplit_rabin.c @@ -0,0 +1,60 @@ +#include "limb/nextsplit.h" +#include "limb/rabin-tables.h" + +/* This is based on rabin fingerprint, using a miw of two different variants : + * - for avg < 128 KiB the double-mask variant, which uses a large mask when avg + * hasn't yet been reached, then a smaller mask afterwards + * - from avg >= 128 KiB the TTTD variant, which also uses two masks, the + * "standard" one and a smaller/fallback one used only when the first one + * didn't find a split. + * Using those two (in that manner) is just the result of some testing, in an + * attempt to get the best deduplication results. + */ + +size_t +nextsplit_rabin(size_t min, size_t avg, const void *data, size_t dlen) +{ + if (dlen <= min) return dlen; + + u8 window[RABIN_WINDOW_SIZE] = { 0 }; + const u8 *b = data; + size_t wpos = 0, pos = min, last = 0; + u64 fp = 0; + u64 mask, lrg_mask; + int tttd = avg >= (128 << 10); + + if (tttd) + mask = (1 << (msb64(avg ) - 1)) - 1; + else + mask = (1 << (msb64(avg * 2) - 1)) - 1; + lrg_mask = (1 << (msb64(avg / 2) - 1)) - 1; + +#define TTTD_BREAK 0x78 +#define APPEND8(p,m) (((p) << 8) | m) ^ rabin_tables.T[(p) >> 55] + for (;;) { + u8 old = window[wpos]; + window[wpos] = b[pos]; + fp = APPEND8(fp ^ rabin_tables.U[old], window[wpos]); + if (++wpos == sizeof(window)) wpos = 0; + + if (pos == dlen) { + if (last) return last; + return pos; + } + if (pos >= min) { + if (tttd) { + if ((fp & lrg_mask) == TTTD_BREAK) { + if ((fp & mask) == TTTD_BREAK) + return pos; + last = pos; + } + } else if (pos < avg) { + if ((fp & mask) == mask) + return pos; + } else if ((fp & lrg_mask) == lrg_mask) { + return pos; + } + } + ++pos; + } +} diff --git a/src/tools/mkrabintables.c b/src/tools/mkrabintables.c new file mode 100644 index 0000000..56ae8fe --- /dev/null +++ b/src/tools/mkrabintables.c @@ -0,0 +1,110 @@ +#include <skalibs/uint16.h> +#include "limb/int.h" +#include "limb/output.h" + +const char *PROG = "mkrabintables"; + +#define RABIN_FINGERPRINT 0xBFE6B8A5BF378D83ULL + +struct rabin { + u16 window_size; + u64 T[256]; + u64 U[256]; +}; + +#define MSB64 (1ULL << 63) + +static u64 +polymod(u64 nh, u64 nl, u64 d) +{ + int i; + int k = msb64(d) - 1; + d <<= 63 - k; + + if (nh) { + if (nh & MSB64) + nh ^= d; + for (i = 62; i >= 0; --i) + if (nh & ((u64) 1) << i) { + nh ^= d >> (63 - i); + nl ^= d << (i + 1); + } + } + for (i = 63; i >= k; --i) + if (nl & 1LL << i) + nl ^= d >> (63 - i); + return nl; +} + +static u64 +polymmult(u64 x, u64 y, u64 d) +{ + u64 h = 0, l = 0; + if (x & 1) + l = y; + for (int i = 1; i < 64; ++i) + if (x & (1LL << i)) { + h ^= y >> (64 - i); + l ^= y << i; + } + return polymod(h, l, d); +} + +static void +init_tables(struct rabin *r) +{ + int i; + int xshift = msb64(RABIN_FINGERPRINT) - 1; + + u64 T1 = polymod(0, 1LL << xshift, RABIN_FINGERPRINT); + for (i = 0; i < 256; ++i) { + r->T[i] = polymmult(i, T1, RABIN_FINGERPRINT) | ((u64) i << xshift); + } + +#define SHIFT 55 /* == xshift - 8 */ +#define APPEND8(p,m) (((p) << 8) | m) ^ r->T[(p) >> SHIFT] + u64 sizeshift = 1; + for (i = 1; i < r->window_size; ++i) + sizeshift = APPEND8(sizeshift, 0); + + for (i = 0; i < 256; ++i) + r->U[i] = polymmult(i, sizeshift, RABIN_FINGERPRINT); +} + +#include <stdio.h> +#include <inttypes.h> + +int +main(int argc, char **argv) +{ + struct rabin r = { .window_size = 32, }; + + if (argc == 2) { + if (!uint160_scan(argv[1], &r.window_size)) + retw(1, "invalid window size"); + } else if (argc != 1) { + dieusage(1, "[WINDOWSIZE]"); + } + + init_tables(&r); + + printf( "#ifndef LIMB_RABIN_TABLES_H\n" + "#define LIMB_RABIN_TABLES_H\n" + "\n" + "#include \"limb/int.h\"\n" + "\n" + "#define RABIN_WINDOW_SIZE %u\n" + "\n" + "static struct {\n" + " u64 T[256];\n" + " u64 U[256];\n" + "} rabin_tables = { {\n", r.window_size); + for (int i = 0; i < 256; ++i) + printf("U64_C(%" PRIu64 ")%s", r.T[i], (i == 255) ? "" : ((i + 1) % 4) ? ", " : ",\n"); + printf("\n}, {\n"); + for (int i = 0; i < 256; ++i) + printf("U64_C(%" PRIu64 ")%s", r.U[i], (i == 255) ? "" : ((i + 1) % 4) ? ", " : ",\n"); + printf("\n} };\n#endif /* LIMB_RABIN_TABLES_H */"); + + return 0; +}