Benchmarks

mdtest

The metadata benchmark from the IOR suite: MPI processes create, stat, open and remove empty files, and create, stat, rename and remove directories, in one shared directory or one each. We run it through sandbox.exec in a LayerFS v1.0.0 Workspace, on a FUSE baseline (libfuse’s passthrough_ll), and on the kernel's own overlayfs, one after the other.

LayerFS against FUSE baseline

vs FUSE baseline
KindPerformancemetadataindustry standard
LayerFSv1.0.0
Ran on
LayerFS
core at main e5c7aaae05, through the JS SDK
Tool
IOR mdtest f1172add · Open MPI 4.1.4, 1 and 4 procs
Baseline
libfuse passthrough_ll 3.14.0, a sibling container on the same VM
Reference
kernel overlayfs, the same Sandbox's own /tmp (no FUSE)
Host
arm64 Mac · Docker Desktop, linux/arm64, 8 CPUs, 8.3 GB
Load
7.9–15.7 (1 min), three-arm run

Completeness and the Commit

LayerFS v1.0.0

9 / 9 exit 0

every case, 1 and 4 MPI processes; the FUSE baseline finished 7 of 9, 2 reached the 115 s ceiling

one Commit of 20,000 files mdtest created

2.6 s

130 µs per file, mountable as a Commit

Against a FUSE baseline

The like-for-like comparison: libfuse’s passthrough_ll (fuse-3.14.0) over a plain directory, writeback off, same mpirun command, same run. LayerFS records every change for the next Commit as it goes.

create, rename, remove · 1 process

0.86–1.28×

on par with the baseline

create, rename, remove · 4 processes

1.21–4.22×

ahead in every phase both finished

stat and open/close

2.94×–784×

a cache setting, not daemon speed (below)

CaseItems / proccreationstatread / renameremoval
files · 1p shared20,0001.28×5,697 / 4,45563.2×696,185 / 11,0203.29×16,781 / 5,0960.984×6,811 / 6,922
files · 1p unique20,0001.12×4,143 / 3,70749.4×526,744 / 10,6572.94×15,622 / 5,3170.864×5,689 / 6,586
files · 4p shared20,0001.21×4,340 / 3,598659×3,365,877 / 5,1049.58×55,366 / 5,7811.48×5,846 / 3,949
files · 4p unique20,0003.62×11,597 / 3,206784×3,007,855 / 3,83914.9×55,296 / 3,7083.81×12,455 / 3,271
dirs · 1p shared20,0000.928×6,136 / 6,61375.2×880,056 / 11,6981.28×6,311 / 4,9400.953×6,149 / 6,455
dirs · 1p unique20,0001.06×5,865 / 5,51373.6×754,514 / 10,2581.14×5,750 / 5,0321.04×6,589 / 6,329
dirs · 4p shared20,000baseline reached the 115 s ceiling · LayerFS finished: 6,440 / 2,845,573 / 6,600 / 6,702 ops/s
dirs · 4p unique20,000baseline reached the 115 s ceiling · LayerFS finished: 12,667 / 2,864,717 / 13,756 / 12,105 ops/s
tree, files10,5004.22×7,072 / 1,674657×2,404,531 / 3,66116.4×58,623 / 3,5812.38×7,902 / 3,322
tree, dirs10,5003.27×7,323 / 2,240422×2,382,160 / 5,6501.71×7,721 / 4,5171.52×8,007 / 5,272

LayerFS ÷ FUSE baseline in each cell, then both ops/s (LayerFS / baseline). Above 1× LayerFS is faster; within 15 % shown as on par. "unique" gives each process its own directory.

What the comparison says

  • One process: on par

    Create, rename and remove run at 0.86–1.28× of the baseline, while LayerFS also records each change. It trails in 4 of those 10 phases (removal, and creating directories in one shared directory), by up to 14 %.

  • Four processes: ahead

    With four processes LayerFS is 1.21–4.22× the baseline in every write phase both finished. The baseline gets slower with four processes; with a directory each, LayerFS does (11,597 file creates/s against 4,143 for one).

  • Stat and open/close are a cache setting

    LayerFS answers with a 60 s entry and attribute timeout, so the kernel serves repeat stats from cache. passthrough_ll keeps its default 1.0 s timeout, so its stats reach the daemon. That lead is a setting, not daemon speed.

  • Two cases the baseline did not finish

    With four processes making directories, passthrough_ll reached the 115 s ceiling; LayerFS finished both. They are left out of the ranges above.

The reference is not like-for-like

Kernel overlayfs is the Sandbox's own overlayfs /tmp: the kernel's in-memory path, no FUSE. It is the ceiling for this host, not a peer:

  • No FUSE hop

    The reference runs inside the kernel. Every LayerFS operation is a FUSE request to its daemon, a round trip every FUSE filesystem pays.

  • Writes reach the daemon

    Kernel writeback caching is off under the FUSE contract, so each buffered write reaches LayerFS; the reference only copies into page cache.

  • Every change is recorded

    LayerFS records each change so a Commit can capture it and mount it again later. The reference keeps nothing once the container goes.

  • A ceiling, not a peer

    Ratios show the cost of those properties against the fastest local path on this host. The like-for-like comparison is the FUSE baseline, libfuse’s passthrough_ll, measured in the same run.

LayerFS ÷ kernel overlayfs, phase by phase

mdtest's own ops/s as recorded; each cell shows LayerFS ÷ kernel overlayfs, then LayerFS / FUSE baseline / kernel overlayfs. In the write phases the baseline sits about as far below the kernel path as LayerFS, or further: most of the gap is the FUSE round trip itself. Stat is at or above the kernel path in 8 of 10.

CaseItems / proccreationstatread / renameremoval
files · 1p shared20,0000.061×5,697 / 4,455 / 93,5220.970×696,185 / 11,020 / 717,6740.031×16,781 / 5,096 / 532,8810.039×6,811 / 6,922 / 174,684
files · 1p unique20,0000.059×4,143 / 3,707 / 69,9170.670×526,744 / 10,657 / 786,5990.029×15,622 / 5,317 / 534,2040.032×5,689 / 6,586 / 175,339
files · 4p shared20,0000.052×4,340 / 3,598 / 83,7952.34×3,365,877 / 5,104 / 1,436,5280.056×55,366 / 5,781 / 996,7720.033×5,846 / 3,949 / 179,697
files · 4p unique20,0000.053×11,597 / 3,206 / 216,9542.14×3,007,855 / 3,839 / 1,403,9280.056×55,296 / 3,708 / 989,1560.029×12,455 / 3,271 / 423,684
dirs · 1p shared20,0000.172×6,136 / 6,613 / 35,7191.12×880,056 / 11,698 / 787,0940.063×6,311 / 4,940 / 100,2540.053×6,149 / 6,455 / 116,217
dirs · 1p unique20,0000.178×5,865 / 5,513 / 33,0251.17×754,514 / 10,258 / 643,3770.062×5,750 / 5,032 / 92,0390.070×6,589 / 6,329 / 94,443
dirs · 4p shared20,0000.301×6,440 / – / 21,4161.94×2,845,573 / – / 1,463,2970.079×6,600 / – / 83,1550.094×6,702 / – / 71,513
dirs · 4p unique20,0000.203×12,667 / – / 62,3491.52×2,864,717 / – / 1,884,7420.042×13,756 / – / 327,6000.042×12,105 / – / 287,635
tree, files10,5000.046×7,072 / 1,674 / 153,7232.09×2,404,531 / 3,661 / 1,152,0400.069×58,623 / 3,581 / 849,6680.032×7,902 / 3,322 / 249,482
tree, dirs10,5000.217×7,323 / 2,240 / 33,7501.36×2,382,160 / 5,650 / 1,752,7740.050×7,721 / 4,517 / 153,3840.057×8,007 / 5,272 / 140,144

Read with care

  • A sibling container

    A Sandbox cannot host a second FUSE mount (no-new-privileges, no user namespaces), so passthrough_ll runs in a sibling container from the same image, on the same VM and kernel; the tool runs as the same uid 501.

  • Its own defaults

    passthrough_ll runs as root with no credential switch, up to 10 threads and a 1.0 s cache timeout. It keeps nothing beyond the directory: no recording, no Commit.

  • It slows with size

    passthrough_ll finds inodes in one mutex-guarded list, so it slows as the file count grows; each case holds 20,000 items per process. An indexed server (passthrough_hp) was not measured.

  • Empty files

    mdtest writes nothing (-w 0 -e 0), so read is an open and close. Data rates are on the fio page.

  • One sample each

    One run per case and arm, host load 7.9–15.7 (1 min). Cache state warm/unknown, not cold. An earlier two-arm run is in the data folder.

  • This host

    Docker Desktop on an arm64 Mac, LayerFS at main e5c7aaae05, mdtest from IOR f1172add, Open MPI 4.1.4, Disposable Store.

Data: benchmark/data/mdtest/v1.0.0/three-arm/ in the LayerFS repository (every phase, the Commit, environment, receipts and the run record).