Benchmarks

fsstress

xfstests' stress generator: thousands of random creates, writes, renames, links, truncates, mapped and direct I/O from several processes at once. We run it through sandbox.exec in a LayerFS v1.0.0 Workspace and on the Sandbox's own disk, then commit the Workspace, mount that Commit again and compare every entry.

LayerFS against kernel overlayfs

vs kernel overlayfs
KindCorrectnessconcurrencyCommit & historyindustry standard
LayerFSv1.0.0
Ran on
LayerFS
core at main e5c7aaae05, through the JS SDK
Tool
xfstests ltp/fsstress at 22348afe338c
Reference
kernel overlayfs, the same Sandbox's own /tmp (no FUSE)
Host
arm64 Mac · Docker Desktop, linux/arm64, 8 CPUs, 8.3 GB
Load
5.9–6.7 (1 min)

Capture proof

Commit → fork → mount → compare

3,755 / 3,755 IDENTICAL

every entry after 4 × 10,000 concurrent ops: path, type, mode, size, SHA-256, symlink target, links

5.21 GB of file bytes captured

the Commit

4.85 s

5.21 GB logical · 1.28 MB newly stored

The stress runs

Every run finished with exit 0 on both arms. Errors count every refused call, including root-only and XFS-only ones that hit both arms.

CaseArmProcs × opsExitWallRefused calls
c1 seed 365001Kernel overlayfs1 × 10,00002.45 s2,150
c1 seed 365001LayerFS1 × 10,00003.82 s3,621
c2 seed 365002Kernel overlayfs4 × 10,00003.96 s8,443
c2 seed 365002LayerFS4 × 10,00006.93 s14,638

Op by op

Share of attempted calls that succeeded, concurrent case c2. 42 of 61 operation kinds behaved alike on both arms (within five points); these 19 did not. deduperange is left out: the reference returned success but deduplicated nothing.

  1. fallocate()0%100%
  2. fallocate(COLLAPSE_RANGE)0%20%
  3. fallocate(INSERT_RANGE)0%27%
  4. fallocate(KEEP_SIZE)0%100%
  5. fallocate(KEEP_SIZE|PUNCH_HOLE)0%100%
  6. fallocate(KEEP_SIZE|ZERO_RANGE)0%100%
  7. fallocate(ZERO_RANGE)0%100%
  8. getattr0%52%
  9. getfattr0%21%
  10. ioctl(FIEMAP)0%51%
  11. link68%100%
  12. listfattr0%100%
  13. removefattr0%18%
  14. rename(EXCHANGE)1%100%
  15. rmdir16%10%
  16. setfattr0%98%
  17. setxattr0%6%
  18. xfsctl(XFS_IOC_RESVSP64)0%100%
  19. xfsctl(XFS_IOC_UNRESVSP64)0%100%
LayerFSKernel overlayfs

Outside the format, by design

The calls only LayerFS refused, each reproduced with a standard tool and each a limit LayerFS keeps on purpose. fsstress keeps going past a refused call, so none of them broke a run or the capture.

Gapfsstress opsLayerFS returnsDeliberate limitCalls c1 / c2
user.* extended attributessetfattr, getfattr, listfattr, removefattrEOPNOTSUPPA narrow FUSE surface516 / 2,013
fallocate (every mode) and XFS RESVSP/UNRESVSPfallocate(*), xfsctl(XFS_IOC_RESVSP64 / UNRESVSP64)EOPNOTSUPPA narrow FUSE surface593 / 2,269
renameat2(RENAME_EXCHANGE)rename(EXCHANGE)EINVALPlain renames188 / 764
hard link to a symlinklinkEPERMFile hard links31 / 128
FS_IOC_GETFLAGS / FSGETXATTR / FIEMAPgetattr, setxattr (FS_IOC), ioctl(FIEMAP)ENOTTY / EOPNOTSUPPA narrow FUSE surface276 / 1,241

How it ran

Only through the JS SDK

  1. project.init(a seed folder)the Store
  2. sandbox.create("bench", image)fsstress built in
  3. branch.fork("bench", "main")its own Branch
  4. sandbox.mount("bench", "bench")/workspaces/1
  5. sandbox.exec(fsstress, manifest)both arms
  6. sandbox.commit(after c2)the whole Workspace
  7. branch.fork("capture", Commit id)a Branch at that Commit
  8. sandbox.mount("bench", "capture")/workspaces/2, manifest again
  • Not identical work

    A refused call returns early and later ops pick different files, so the arms do different work. Compare them op by op, not by wall time.

  • One sample each

    Seeds fixed, each case once per arm. Host load 5.94-6.24 (1 min) during the cases.

  • Not counted

    deduperange (the reference deduplicated nothing either), and root-only or XFS-only calls that fail on both arms.

  • This host

    Docker Desktop on an arm64 Mac, LayerFS at main e5c7aaae05, Disposable Store.

Data: benchmark/data/fsstress/v1.0.0/ in the LayerFS repository (runs, per-op outcomes, capture, environment, receipts and the run record).