diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:14:38 +0200 |
| commit | 3443c1c65e9f1753e1e656b35d08416c1fa298f2 (patch) | |
| tree | 4e43236f460145a4d75d1b4616dcb7aa6ef08f51 /dedupe.py | |
| download | worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.tar.gz worldmap-viewer-3443c1c65e9f1753e1e656b35d08416c1fa298f2.zip | |
worldmap-viewer: initial public history
Diffstat (limited to 'dedupe.py')
| -rw-r--r-- | dedupe.py | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/dedupe.py b/dedupe.py new file mode 100644 index 0000000..691e6f2 --- /dev/null +++ b/dedupe.py @@ -0,0 +1,75 @@ +"""Share identical blocks between files on file systems that support it (btrfs, XFS): the kernel compares the bytes +itself and only then lets the two files point at the same disk blocks (FIDEDUPERANGE), so contents never change. +Serve caches of two eras are mostly the same bytes at the same offsets (only the areas an era changes differ): +sharing them roughly halves the disk they take. Best effort: anywhere else it does nothing.""" +from __future__ import annotations + +import fcntl +import os +import struct +from pathlib import Path + +FIDEDUPERANGE = 0xC0189436 # _IOWR(0x94, 54, struct file_dedupe_range) +BLOCK = 128 << 10 # compare in btrfs compressed-extent units +MAX_CALL = 16 << 20 # btrfs dedupes at most 16 MiB per call +READ = 16 << 20 + + +def _ioctl(src_fd: int, dst_fd: int, off: int, length: int) -> int: + """Ask the kernel to share [off, off+length) of dst with the same range of src; returns bytes shared.""" + buf = bytearray(struct.pack("<QQHHI", off, length, 1, 0, 0) + struct.pack("<qQQiI", dst_fd, off, 0, 0, 0)) + fcntl.ioctl(src_fd, FIDEDUPERANGE, buf) + _, _, done, status, _ = struct.unpack_from("<qQQiI", buf, 24) + return done if status == 0 else 0 + + +def same_runs(a, b, size: int, block: int = BLOCK): + """(offset, length) runs of whole blocks that are byte-identical in the two open files.""" + runs, start, off = [], None, 0 + while off + block <= size: + n = min(READ, (size - off) // block * block) + x, y = os.pread(a, n, off), os.pread(b, n, off) + for k in range(0, n, block): + if x[k:k + block] == y[k:k + block]: + if start is None: + start = off + k + elif start is not None: + runs.append((start, off + k - start)) + start = None + off += n + if start is not None: + runs.append((start, off - start)) + return runs + + +def dedupe_file(src: Path, dst: Path) -> int: + """Share dst's blocks that equal src's (same offsets). Bytes shared; 0 if unsupported or nothing matches.""" + size = os.path.getsize(src) + if size != os.path.getsize(dst) or size < BLOCK: + return 0 + a, b = os.open(src, os.O_RDONLY), os.open(dst, os.O_RDONLY) + try: + shared = 0 + for off, length in same_runs(a, b, size): + for o in range(off, off + length, MAX_CALL): + shared += _ioctl(a, b, o, min(MAX_CALL, off + length - o)) + return shared + except OSError: # tmpfs, ext4, permissions: nothing to share + return 0 + finally: + os.close(a) + os.close(b) + + +def dedupe_dirs(new: Path, peers) -> int: + """Share new's files with same-named, same-sized files in peer folders. Bytes shared.""" + shared = 0 + for peer in peers: + for f in sorted(Path(new).iterdir()): + p = Path(peer) / f.name + try: + if f.suffix == ".npy" and p.is_file(): + shared += dedupe_file(p, f) + except OSError: # a peer pruned meanwhile: skip it + pass + return shared |
