# Comparison

Performance and correctness comparisons between `fifi` and other
duplicate-finder tools on real-world datasets. The goal is to build a
body of evidence for how `fifi` behaves across different workloads —
runtime, what it reports, and how its output relates to other tools'.

Each dataset lives in its own subdirectory under `comparison/`,
containing the raw tool outputs and any per-dataset notes. The summary
table below is the at-a-glance view; see the per-dataset sections for
detail.

## Summary

| Dataset                 | Tool          | Wall time | Groups  | Paths in groups |
|-------------------------|---------------|----------:|--------:|----------------:|
| [mnt_steam](#mnt_steam) | fdupes 2.4.0    | 26.5 s    | 12,532  | 58,232          |
| [mnt_steam](#mnt_steam) | fdupes 2.4.0 -H | 32.1 s    | 20,344  | 77,864          |
| [mnt_steam](#mnt_steam) | fifi 0.3.2      |  5.4 s    | 12,499  | 60,122          |

Caveat: with only one dataset, these numbers are an indication, not a
benchmark.

## Datasets

### mnt_steam

Steam library on a 1.9 TB btrfs SSD (`/dev/sdb1`, ~1.1 TB used,
`compress=zstd:3,ssd,discard=async`). Contents include game binaries,
Unity/Mono asset bundles, several Proton runtimes, the pressure-vessel
sandbox tree under `var/tmp-*/`, and workshop content (mostly XML).
Notable composition: many hardlinks (Proton/runtime libraries share
inodes), some reflinks, and ~2,000 zero-byte placeholder files in
*Carmageddon TDR 2000*.

**Host:** Arch Linux, kernel 7.0.9-arch1-1.
**Cache state:** hot page cache — each tool was run several times in
succession before the recorded output was kept.

#### Runtime

| Tool          | Wall    | usr      | sys      |
|---------------|--------:|---------:|---------:|
| `fdupes -r`   | 26.5 s  | 22.4 s   |  4.0 s   |
| `fdupes -rH`  | 32.1 s  | 26.5 s   |  4.8 s   |
| `fifi`        |  5.4 s  |  4.5 s   | 11.3 s   |

`fifi`'s parallel hashing pushes most of its time into the kernel
(`sys`), which is expected for an I/O-bound workload using all cores at
once. fdupes is single-threaded and stays in user space.

#### What each tool reports

| Tool          | Groups | Paths in groups |
|---------------|-------:|----------------:|
| fdupes        | 12,532 |          58,232 |
| fdupes -H     | 20,344 |          77,864 |
| fifi          | 12,499 |          60,122 |

Comparing fifi against `fdupes -H` (closest apples-to-apples — fdupes'
default suppresses hardlink siblings, fifi lists them as aliases):

| Bucket                                          | Groups |
|-------------------------------------------------|-------:|
| Identical group on both sides                   | 12,498 |
| Same anchor path, differing membership          |      1 |
| Only in `fdupes -H`                             |  7,845 |
| Only in `fifi`                                  |      0 |

Of the 7,845 groups present only in `fdupes -H`:

- **7,812** are pure hardlink sets — every member shares an inode with
  every other. `fifi` renders these as aliases under the canonical
  entry rather than as separate groups, because they represent no
  reclaimable disk space.
- **33** are groups of zero-byte files (~1,970 of them are the empty
  `.h` files in *Carmageddon TDR 2000*). `fifi` skips empty files.
- The **1** "differing membership" group is `steam_autocloud.vdf` (64
  members in fifi, 65 in fdupes); the missing entry lives in a
  dot-directory (`.webpack/`), which `fifi` skips by default.

#### Correctness check

For 300 sampled groups where `fifi` added paths beyond what default
`fdupes` reported, every extra was classified by inspecting the file on
disk:

| Verdict                       | Pairs |
|-------------------------------|------:|
| Hardlink to a known member    |   692 |
| Byte-identical, not a hardlink|     0 |
| Different bytes               |     0 |

Every "extra" fifi reports is a hardlink alias that fdupes default
suppresses. No false positives observed.

#### Files

- `comparison/mnt_steam/fdupes`   — `fdupes -r /mnt/steam`
- `comparison/mnt_steam/fdupes_H` — `fdupes -rH /mnt/steam`
- `comparison/mnt_steam/fifi`     — `fifi /mnt/steam`

## Methodology

For each dataset, both tools are pointed at the same root and their
output is captured with `fish`'s `time` builtin into a file in
`comparison/<dataset>/`. `fifi` is run from a release build
(`target/release/fifi`); debug builds are 10–50× slower and not
representative.

State the cache state when recording runs. Page-cache effects are
significant: a cold-cache run can be roughly double a warm one for
fdupes on a large tree. When in doubt, do a warm-up pass and report
the warm number, or report both.

Parsing and comparing outputs:

- [`compare.py`](./compare.py) parses both formats (strips fifi's tree
  prefix; skips the `fish` time trailer), splits on blank lines into
  groups, sorts each group, and merge-walks the two lists by anchor
  path. Reports identical / shared-anchor-differing / one-side-only
  buckets, and a subset breakdown for the differing case.
- [`verify_extras.py`](./verify_extras.py) takes groups that one tool
  extended and checks each extra against the rest of its group on disk
  (hardlink / byte-identical / mismatch). Useful for ruling out false
  positives.

## Adding a new dataset

1. Create `comparison/<dataset>/` and capture both tools' output into
   files named after the tool (`fdupes`, `fdupes_H`, `fifi`, ...).
2. Add a row per tool to the [Summary](#summary) table.
3. Add a new `### <dataset>` section under [Datasets](#datasets) with
   the host/environment, cache state, runtime table, output-overlap
   table, and any noteworthy composition (lots of hardlinks?
   reflinks? small files? deep tree?).
4. Run a sample through `verify_extras.py` and record the verdict if
   the outputs disagree.
