mothcdc
Formerly mincatcdc. A fork of MinCDC that adds a caterpillar layer for metadata-efficient content-defined chunking on redundant data. The algorithm described below is MinCDC; see This fork for what's added and why.
MinCDC is a very simple yet efficient content-defined chunking algorithm. It splits your input data into chunks in such a way that the boundaries are defined by the data itself. This means duplicate regions in large (sets of) files are likely to have identical boundaries and can thus efficiently be found and deduplicated.
To start using mothcdc add the following to your Cargo.toml:
[dependencies]
mothcdc = "0.6"
Please refer to the documentation for more information on usage.
This fork: mothcdc
MinCDC — the algorithm and its SIMD core — is Orson Peters' work. This fork adds a caterpillar layer, an idea from the Chonkers algorithm (Berger, 2025), with vector acceleration in the style of VectorCDC (Udayashankar et al., FAST '25).
When data has a long run of repeated bytes — zeros, padding, the same block over and over — mincdc cuts it into a flood of tiny chunks. Each chunk is a record you have to store and track. The bytes dedupe down to almost nothing, but you still keep every record: a mostly-empty 200 MiB disk image becomes ~182,000 of them.
The caterpillar collapses any run of byte-identical adjacent chunks into a single record with a repeat count. On that disk image: 182,701 → 7,798 records (−96%), with no change to what is stored. A SIMD scan proves a run once and skips the boundary search inside it, so runs chunk several times faster than plain mincdc. A cheap probe keeps the cost near zero (~1%) on data with no runs. When streaming, runs survive buffer refills, so a multi-GB zero region is still one record.
The crate also ships a C API (--features capi) and a
dedup-bench fork
so it can be measured by the same harness as every other chunker.
Benchmarks
All numbers come from UWASL dedup-bench
(the VectorCDC evaluation harness), with mothcdc plugged in as a chunking
technique — same loop, same timers, same dedup measurement as every other
algorithm. One AMD EPYC/AVX2 machine, one session, ~8 KiB target chunks.
Corpora: a raw Debian VM image (768 MiB), DEB .ova appliances (3 GiB, FAST
'25 dataset), six consecutive linux-6.6.x source tars (8 GiB, stand-ins for
backup generations), and enwik9 (1 GiB of Wikipedia text, never used for
tuning). mothcdc uses the recommended window (min=4096, max=12288); the
"wide" row is (2048, 14336).
Throughput, GiB/s:
| algorithm | raw VM | DEB .ova | LNX | enwik9 |
|---|---|---|---|---|
| AE-Min / FastCDC / RAM | 1.4 / 1.8 / 1.5 | 1.4 / 2.0 / 1.4 | 1.4 / 1.9 / 1.6 | 1.4 / 1.9 / 1.9 |
| SeqCDC | 3.2 | 6.4 | 7.1 | 5.9 |
| VectorCDC AE-Min (AVX2) | 14.0 | 5.4 | 7.9 | 8.5 |
| mothcdc (wide) | 11.4 | 10.7 | 9.3 | 8.0 |
| mothcdc | 16.2 | 15.2 | 14.0 | 13.1 |
| VectorCDC RAM (AVX2) | 23.4 | 19.6 | 24.7 | 29.6 |
Space savings (dedup-bench measure-dedup; same result on any machine):
| algorithm | raw VM | DEB .ova | LNX |
|---|---|---|---|
| mothcdc | 53.3% | 5.2% | 60.3% |
| mothcdc (wide) | 53.2% | 4.9% | 59.2% |
| SeqCDC | 52.4% | 4.0% | 56.5% |
| FastCDC | 51.8% | 4.8% | 52.4% |
| AE-Min | 50.0% | 3.5% | 55.5% |
| RAM / VectorCDC RAM | 51.4% | 3.2% | 49.2% |
In short: best dedup on every corpus at every chunk size tested (4K, 8K, 16K), and the fastest chunker except VectorCDC RAM — which has the worst dedup of any algorithm here. On the VM image (long zero runs) the caterpillar turns 232k chunks into 56k records; on data without runs, records equal chunks and the layer costs ~1%.
Caveats. mincdc runs slightly under the chunk-size target where others run over, so normalizing for chunk size would trim some of the dedup edge over AE-Min. SeqCDC ties plain mincdc's dedup at 16K targets. enwik9 shows the speed holds on data we never tuned on, but says nothing about dedup — a single text file has ~0% duplication for every algorithm. Absolute GiB/s varies across shared hosts; compare within a column.
To reproduce: the corpus is public
(https://mincatcdc-bench-corpus.t3.storage.dev/corpus/MANIFEST.sha256,
checksums included), the harness fork is linked above, and
BENCH_CORPUS=<dir> cargo bench runs the in-repo bench on your own files.
No build flags needed — SIMD width is detected at runtime (SSE2/AVX2/AVX-512
on x86_64, NEON on aarch64). Older tables live in CHANGELOG.md and
examples/.
Choosing min/max
Prefer a narrow window with a high minimum. At the same ~8 KiB average,
(4096, 12288) chunks 40–50% faster than (2048, 14336) with equal or
better dedup on every corpus measured — the boundary search scans max - min
bytes per chunk. (This is the same window as upstream's "MinCDCHash4-l"
below.) Widening max buys fewer records but costs speed and dedup.
cargo run --release --example frontier -- <paths> prints the trade-off on
your own data. The hash constants make no measured difference; keep the
defaults.
Using the caterpillar
It's a drop-in over the byte-slice chunker — iterate Segments instead of
Chunks:
use ;
for seg in new
CaterpillarChunker works on an in-memory slice; for inputs larger than memory,
CaterpillarReadChunker does the same coalescing over a streaming reader in
bounded memory (it yields borrowed Segments valid until the next call, like
ReadChunker). Runs coalesce across buffer refills: a run's unit is copied
once — at most max_size bytes — when it crosses a refill, so even a
multi-gigabyte zero region is a single record. Everything else stays
zero-copy.
(An experimental second tier — content-defined period detection for
phase-rotating runs — was evaluated and removed: mincdc self-aligns to most
periods so it rarely helped, and it cost 76–99% throughput. See
examples/CATBENCH_RESULTS.md and the proto/caterpillar-period branch.)
To get the disk-image number, two 200 MiB APFS images were created
with hdiutil (hdiutil create -size 200m -fs APFS ...), each holding a real
source tree (the second also holds an extra version), so each image is ~92%
written-zero free space — not sparse holes, so SEEK_HOLE would not skip them.
Both images were chunked with cargo run --release --example catbench at
min=2048, max=14336. Plain mincdc produced 182,701 records; the caterpillar
produced 7,798, with identical deduplicated content. Full method and the other
corpora (Linux kernels, containers, SQLite, source trees) are in
examples/REALBENCH_RESULTS.md. Obviously the benchmark is specific to this case. YMMV.
This fork also fixes a soundness bug in the upstream SIMD prefetch and adds test coverage (cross-SIMD-width determinism, an invariant/oracle harness).
Algorithm
The basic idea of MinCDC is to choose chunk boundaries based on the minimum
value of a sliding window over the input data. That is, if the desired chunk
size is between min_size and max_size, we find some min_size <= i <= max_size such that evaluate(bytes[i - w..i]) is minimized, where w is the
window size, breaking ties by choosing the earliest such i. Then we return
chunk bytes[..i] and repeat the process on the remainder bytes[i..].
This library provides two SIMD-accelerated implementations of MinCDC, both with a window size of 4:
MinCDC4, where the evaluation function isu32::from_le_bytes(bytes[i - 4..i]), i.e. a window size of 4 bytes interpreting the bytes as a little-endianu32, andMinCDCHash4, where the evaluation function ishash(u32::from_le_bytes(bytes[i - 4..i])). The hash function used is the very simplehash(x) = x.wrapping_mul(a).wrapping_add(b), for some constantsaandb.
MinCDCHash4 can be slightly (~10%) slower but is far more robust and
predictable, it is the recommended default.
Performance
MinCDC is several times faster than the commonly used
FastCDC while providing a similar amount of
deduplicating power. To benchmark this I downloaded all available Linux kernel
6.x.tar archives (tools/download-linux.sh) and ran the below algorithms on
them, all of them configured to target an expected chunk size of 8 KiB.
To determine the chunking speed I only chunked linux-6.0.tar while the file
was loaded into memory to avoid disk overhead. The dedup% is one minus the total
size of unique chunks divided by the total size of all input files (thus higher
is better). The normalized dedup% is the same percentage acquired from repeating
the experiment with different window sizes until the mean chunk size matched 8
KiB (+/- 1%). This is important when comparing deduplication power since
smaller chunks typically means better deduplication.
| Algorithm | AMD 9950X | Apple M2 Pro | Dedup% | Mean Chunk Size | Dedup% (normalized) |
|---|---|---|---|---|---|
| MinCDCHash4-s | 41.3 GB / s | 23.8 GB / s | 61.08% | 8015 | 60.92% |
| MinCDCHash4-l | 44.5 GB / s | 15.7 GB / s | 61.57% | 8221 | 61.57% |
| MinCDC4-s | 41.7 GB / s | 26.1 GB / s | 62.11% | 7383 | 60.52% |
| MinCDC4-l | 42.0 GB / s | 16.9 GB / s | 64.51% | 6436 | 60.69% |
| FastCDC-s | 6.6 GB / s | 4.1 GB / s | 54.38% | 12866 | 61.81% |
| FastCDC-l | 5.2 GB / s | 3.2 GB / s | 54.87% | 12764 | ~ 62%* |
Here the "-s" variants use a small window size of 8 KiB +/- 25% (min=6144, max=10240), and the "-l" variants use a larger window size of 8 KiB +/- 50% (min=4096, max=12288). The maximum chunk size was increased further for FastCDC as it inherently has a long tail of chunk sizes (see below), this did not impact chunking speed much.
The normalized dedup% for FastCDC-l is marked with an asterisk because I was unable to get the mean chunk size within 1% of 8KiB. The mean size would suddenly jump from 7741 to 10846 just by making a tiny adjustment in window size. For comparison, MinCDCHash4-l with a mean size of 7741 has a dedup% of 62.49% versus FastCDC-l's 62.75%.
Chunk Distribution
Unlike most other content-defined chunking algorithms, the distribution of chunk
sizes generated by MinCDCHash4 is almost entirely uniform in the range
min_size, max_size. This makes it very predictable and well-behaved; the
expected chunk size is also very close to the mean chunk size. Compare that with
FastCDC's distribution for an expected chunk size of 8 KiB:
| MinCDCHash4 | FastCDC |
|---|---|
While the peak is at 8 KiB as expected for FastCDC, there is a long and heavy tail, increasing the mean chunk size by a lot. MinCDCHash4 never creates a chunk outside of the specified range, except for the last chunk which may be smaller.
There is still a bias towards smaller chunks as MinCDC breaks ties in the minimum value towards the earlier breakpoint, but this bias is relatively small.