118 lines
4.3 KiB
Markdown
118 lines
4.3 KiB
Markdown
# Performance
|
||
|
||
All numbers below were measured, not estimated. Re-run them on your own
|
||
hardware before relying on them; filesystem caches dominate scan timings.
|
||
|
||
## Test machine (2026-10-08)
|
||
|
||
| | |
|
||
|---|---|
|
||
| CPU | Intel Core i7-7700 @ 3.60 GHz, 4 cores / 8 threads |
|
||
| RAM | 32 GiB |
|
||
| OS | Linux 7.0, Rust 1.91.1 (release profile: LTO, one codegen unit) |
|
||
| Filesystem | ext4 on Linux software RAID (`/dev/md2`) over two 7200 rpm SATA HDDs |
|
||
|
||
The host is shared; its load average was 5–9 during these runs. Each figure
|
||
is the median (and best) of 7 runs.
|
||
|
||
## Scanner
|
||
|
||
Dataset: a source-package cache directory: 388,307 files + 65,518
|
||
directories (458,510 inventory nodes), 10.5 GiB allocated. **Warm cache
|
||
only**: dropping the page cache needs root, so these measure CPU and syscall
|
||
cost, not disk seeks. Cold-cache behaviour on HDDs will be dominated by seek
|
||
time; expect fewer workers to be optimal there.
|
||
|
||
`mapsize --no-ui --workers N PATH`:
|
||
|
||
| workers | median | best | peak RSS |
|
||
|---:|---:|---:|---:|
|
||
| 1 | 1.52 s | 1.44 s | 75 MB |
|
||
| 8 (balanced default) | 0.47 s | 0.43 s | 76 MB |
|
||
| 32 | 0.46 s | 0.43 s | 80 MB |
|
||
|
||
At 8 workers that is about 1,000,000 entries a second, in under 80 MB for
|
||
458k nodes. Most of the time is spent in the `statx` and `getdents` system
|
||
calls.
|
||
|
||
| other runs over the same tree | median | peak RSS |
|
||
|---|---:|---:|
|
||
| scan + `--largest-files 50` | 0.60 s | 76 MB |
|
||
| scan + `--json` (streamed to /dev/null) | 0.86 s | 76 MB |
|
||
|
||
## Snapshots
|
||
|
||
| operation | median | peak RSS |
|
||
|---|---:|---:|
|
||
| scan + save | 0.75 s | 77 MB |
|
||
| load (`mapsize --no-ui file.msz`) | 0.23 s | 78 MB |
|
||
| load a snapshot written by mapsize 1.2.0 | 0.26 s | 78 MB |
|
||
|
||
Loading decompresses and verifies SHA-256 on one thread while another parses.
|
||
Against a warm cache that is about twice as fast as rescanning; against a
|
||
cold cache or a network filesystem, where scans are dominated by I/O
|
||
latency, the gain is far larger (not yet measured here).
|
||
|
||
## UI
|
||
|
||
`make benchmark` (`examples/bench.rs`), best of 5 runs, on a synthetic
|
||
in-memory tree of 1,008,201 nodes (200 directories × 40 subdirectories × 100
|
||
files, plus one flat directory with 200,000 files), 200×60 terminal:
|
||
|
||
| operation | time per op |
|
||
|---|---:|
|
||
| cold frame at root (sort children, LOD, layout incl. nested previews, paint) | 11.34 ms |
|
||
| resize to a new size (layout + paint, child lists cached) | 1.50 ms |
|
||
| arrow-key move + re-render | 1.31 ms |
|
||
| zoomed into the 200,000-entry directory, cold | 9.37 ms |
|
||
|
||
During a scan the size caches are invalidated at most every 400 ms, so even
|
||
a cold frame costs a few percent of one core. Resize events only record the
|
||
new size; layout happens once per rendered frame, so a resize storm cannot
|
||
queue up work.
|
||
|
||
### Geometry
|
||
|
||
| operation | time |
|
||
|---|---:|
|
||
| squarified layout, 300 items | 8.4 µs |
|
||
| spatial neighbour lookup among 300 blocks | 1.1 µs |
|
||
|
||
### Filtering (1,008,201 nodes)
|
||
|
||
| query | time |
|
||
|---|---:|
|
||
| `size > 1MB` | 26.75 ms |
|
||
| `*.dat AND size > 500k` | 67.84 ms |
|
||
| `path contains sub3` | 63.80 ms |
|
||
|
||
Filters run on a background thread with cancellation and a 120 ms keystroke
|
||
debounce; the UI never waits for them. Path predicates build each path from
|
||
the cached parent directory path into a reused buffer, and `*.ext` globs are
|
||
a suffix test.
|
||
|
||
## Compared with mapsize 1.2.0
|
||
|
||
mapsize 1.2.0 was the last release of the previous implementation, written
|
||
in Go. Both were run alternately on the same machine, over the same tree, in
|
||
the same session:
|
||
|
||
| | 1.2.0 | current | |
|
||
|---|---:|---:|---|
|
||
| scan, 8 workers (median) | 0.63 s | 0.47 s | 1.34× faster |
|
||
| scan, 1 worker (median) | 1.89 s | 1.52 s | 1.24× faster |
|
||
| peak RSS while scanning | 117 MB | 76 MB | |
|
||
| scan + save snapshot | 1.22 s | 0.75 s | 1.63× faster |
|
||
| load snapshot | 0.34 s | 0.23 s | 1.48× faster |
|
||
| scan + JSON export | 1.27 s, 153 MB | 0.86 s, 76 MB | 1.48× faster |
|
||
| cold frame, 1M nodes | 36.01 ms | 11.34 ms | 3.18× faster |
|
||
| squarified layout, 300 items | 84.3 µs | 8.4 µs | 10.07× faster |
|
||
| arrow key + re-render | 1.50 ms | 1.31 ms | 1.14× faster |
|
||
| filter `*.dat AND size > 500k` | 121.40 ms | 67.84 ms | 1.79× faster |
|
||
|
||
## Known costs / future work
|
||
|
||
* Node records could shrink (owner/group/mode/link count are only needed for
|
||
display and some filters) if memory on 10 M+ entry trees matters.
|
||
* Cold-cache HDD benchmarks still need to be run with root access.
|