Content-defined chunking, off by default and priced in the interface.
FastCDC picks boundaries from a rolling hash of the content, so inserting bytes shifts one chunk
instead of every chunk after it. That is the one thing fixed-size framing cannot do, and it is why
this exists.
It is 120 to 160 times slower than the fixed splitter and that is not a bug to optimise away.
Measured in pure PHP on the reference host: 4.29 MB/s reading byte by byte with ord(), 5.63 MB/s
over 64 KiB windows unpacked with unpack, against 683 MB/s for fixed framing plus a BLAKE2b per
frame. An 80 GB initial file capture would spend 5.9 hours of pure CPU here. PHP has no primitive
for a per-byte rolling hash, and the array form that would vectorise it costs roughly 16x the
string in memory, which exhausts a 128 MB limit at 8 MB of input.
So this is reached only from file.shift_detected, only when an administrator has turned it on for
a file type, only with a byte budget per cron run, and never for media or the operation stream.
Two normalisations matter and are easy to get wrong. The mask is widened for chunks below the
target and narrowed above it, which is what makes the size distribution tight rather than
exponential - that is the difference between FastCDC and the older Rabin approach, and it is where
most of its speed comes from. And the minimum size is skipped without hashing, because hashing
bytes that cannot produce a boundary is pure cost.
Measured throughput on the reference host, in bytes per second.
The per-byte figure rather than the windowed one, because the windowed variant is what
exhausts memory on a large buffer and is therefore not what runs.
DEFAULT_TARGET
DEFAULT_TARGET = 16384
Default target chunk size.
MIN_FRACTION
MIN_FRACTION = 0.25
How small a chunk may be, as a fraction of the target.
Below this the per-chunk overhead - a digest, an index row, a pack entry - starts to cost more
than the deduplication saves.
MAX_MULTIPLE
MAX_MULTIPLE = 4
How large a chunk may be, as a multiple of the target.
BUFFER
BUFFER = 262144
How much is read from the stream at a time.
Properties
$gear
$gear : list|null
The gear table the rolling hash indexes.
Derived deterministically rather than shipped as a literal, so two hosts produce identical
boundaries for identical content. A random table would make a chunk's identity depend on which
machine wrote it, which would defeat deduplication across a restore.
Type
array<int, int>|null
—
$target
$target : int
Target chunk size in bytes.
Type
int
$strictMask
$strictMask : int
Mask used below the target, with more bits set so a boundary is harder to hit.
Type
int
$lenientMask
$lenientMask : int
Mask used above the target, with fewer bits set so a boundary is easier to hit.