dwarfs

mhx活跃维护

一个高压缩率的只读文件系统,支持 Linux、FreeBSD、macOS 和 Windows,适合把大量文件打包成镜像后高效挂载读取,兼顾压缩比和访问速度。

A fast high-compression read-only file system for Linux, FreeBSD, macOS and Windows

v0.15.8发布于 2026-09-30 14:55 UTC2,629star +2 / 7 天90fork同步于 2026-11-10 11:43 UTC

怎么上手

由 README 提炼,执行前请确认
mkdwarfs -i /path/to/input/dir -o /path/to/image.dwarfs
mkdir /path/to/mountpoint && dwarfs /path/to/image.dwarfs /path/to/mountpoint
硬件要求按 README 说明

README 未说明具体硬件要求;仅提到多线程实现能利用多核,未给出显存或 CPU 型号限制。

下载:源码 zip·GitHub Releases(安装包)本站不镜像安装包

项目说明

由 README 自动翻译提炼

把目录打包成可秒挂载的只读镜像,靠去重和相似聚类实现超高压缩比

一个高压缩率的只读文件系统,支持 Linux、FreeBSD、macOS 和 Windows,适合把大量文件打包成镜像后高效挂载读取,兼顾压缩比和访问速度。

核心能力

  • 用相似哈希把相似文件聚簇,跨文件边界利用冗余
  • 按类别分别处理文件或片段,跳过不可压缩数据
  • 分段分析压缩后文件系统块,缩小未压缩体积
  • mkdwarfs 与 FUSE 驱动均高度多线程
  • 支持挂载、解包、校验与逐文件校验和生成

容易踩的坑

挂载依赖 FUSE 驱动,需系统支持 FUSE;镜像是只读的,修改内容需重新用 mkdwarfs 打包;README 未说明默认端口等网络配置。

内容由模型根据 README 生成,可能不完整或不准确,以官方文档为准。看原文 README

最近发版

5 个版本v0.15.8
  1. v0.15.8dwarfs-0.15.8最新正式版2026-09-30 · 2026-09-30 14:55 UTC

    Bug fixes

    • Correctly handle sparse files when querying entries in "data order". This is primarily used by dwarfsextract to speed up extraction and avoid repeatedly decompressing the same blocks. All regular files are sorted by the block referenced by the first chunk. While subsequent chunks may also reference earlier blocks, these blocks are typically still in the cache. However, a sparse file starting with a hole will reference a special "hole marker" block in its first chunk, causing all sparse files that start with a hole to be clustered at the end of the list, without considering any of the subsequent data chunks. This is likely an academic edge case, but in an image containing tens of thousands of such sparse files, this can quite dramatically slow down extraction. Furthermore, this also triggers other bugs that can quite easily cause dwarfsextract to run out of memory. This has been fixed by ignoring the holes and only considering the first data chunk when sorting.

    • Limit size of worker group queues in filesystem extractor. During extraction, the filesystem extractor used by dwarfsextract uses a worker pool for writing regular files. This pool has a job queue that by default can grow unbounded. Each job, before being added to the queue, will already trigger an asynchronous read of the file data. This is actually bounded based on the size of the queued regular files, but not considering the amount of memory used by the decompressed blocks in order to actually read the queued file data. This usually isn't a problem, since the files are added in "data order", but in combination with the bug described earlier where sparse files starting with holes end up not actually being sorted, this can cause requests for pretty much all blocks in the image to be decompressed simultaneously. Even worse: if the decompressed blocks are larger than the configured cache size, blocks that are still referenced by eariler requests can be evicted from the cache, causing multiple simultaneous decompression requests for the same block. So even though the decompressed image might actually fit in memory, the actual memory consumption can very easily reach tens of gigabytes. Limiting the queue size is a workaround for this issue, but it's a good idea to bound the queue size anyway. There are two real fixes: one is the fix for the sorting issue described earlier, the other one is for the block cache to keep track of blocks that are evicted, but still in use, and re-insert these blocks into the cache rather than decompressing them again. The latter is a more involved change, so it is not included in this release, and it's really not necessary for the dwarfsextract use case with the former fix in place.

    • Correctly compute total hardlink size. The algorithm used to compute the total hardlink size (i.e. the total size of all hardlinks beyond the first one) was not correctly accounting for non-hardlinked duplicates. The total hardlink size is currently really only used for progress reporting in dwarfsextract. However, v0.15.0 introduced a strict check that would abort the process if there was a mismatch between the total computed size and the total written size if --stdout-progress was used. This strict check would fail if the total hardlink size value was actually used, which is only the case for extracting to formats that do not support hardlinks, e.g. ZIP or 7z. A workaround would be to simply drop --stdout-progress, or to make the strict check just report an error rather than aborting (this is also done in this release). The real fix is to correctly compute the total hardlink size, and fix a wrong value when re-writing an image and using --rebuild-metadata.

    • Don't abort extraction in release builds when discovering a progress mismatch with --stdout-progress. The mismatch is still reported as an error, but the process will continue in a release build and abort in a debug build.

    • Preserve preferred pa

  2. v0.15.7dwarfs-0.15.72026-08-19 · 2026-08-19 18:42 UTC
    查看更新说明

    Bug fixes

    • For fragments that are compressed with an algorithm that requires a granularity greater than 1 byte, sparse files with holes could trigger an error and cause the file to be empty in the output file system if the hole boundaries were not aligned to the granularity. This can easily be the case for 24-bit PCM audio files with long periods of silence, where the source file system has punched holes into the file to save space. The fix is to only treat holes specially if the compression algorithm has a granularity of 1 byte. For other granularities, holes are simply treated as "regular" runs of zeros. This is justifiable since PCM audio files are unlikely to contain gigabytes worth of silence, and even then this it would still be de-duplicated away by the segmenter. Fixes GitHub issue #378.

    • When changing the block size of a DwarFS image, the inode size cache update would only ever add new entries, but never remove inodes that no longer had the minimum number of chunks to qualify for being cached. This could eventually trigger the consistency check in dwarfsck and cause it to fail. The fix is to remove any unqualifying inodes from the cache.

    Full Changelog: https://github.com/mhx/dwarfs/compare/v0.15.6...v0.15.7

    5957c2a380e1c57bc3b18475612be36120c3faf786373fcdf7c15245654869db  dwarfs-0.15.7-Linux-aarch64.tar.xz
    96163598341b3a0c2450a4e58c9ca33ad766b86a915f0fe49b057cac44cf776f  dwarfs-0.15.7-Linux-arm.tar.xz
    04bf6e3a14414c95ad1e4f2c3c8b42e6ec3a4d5a74fe178874cb439f760c6f3c  dwarfs-0.15.7-Linux-i386.tar.xz
    e3c497d164c9df0f705e741607ce03cc372705669ddfd4ad6dd79f056708c922  dwarfs-0.15.7-Linux-loongarch64.tar.xz
    c19de3020a2ac58f0f70d65e349bb356b074fc9de7beea4519cfdd311f0fedf4  dwarfs-0.15.7-Linux-ppc64le.tar.xz
    2aa74a376c74702c45b4ccc3703ce5b5283dfd05084a16b965d6d4771ef50a1a  dwarfs-0.15.7-Linux-ppc64.tar.xz
    481c182e9e175ea3278b2a11b78c387dac5ff0403bd38197f9b8cd00180b8999  dwarfs-0.15.7-Linux-riscv64.tar.xz
    15cbc7e44c7162af3911e82438d3ca4fe53af9551828c1390412bfe64a2e4701  dwarfs-0.15.7-Linux-s390x.tar.xz
    4dc3b756af88ede837eb3f27fbb5f2f0dbb83d7c9933a1d3be9216d2069d2f5f  dwarfs-0.15.7-Linux-x86_64.tar.xz
    363c7fdbf7bad490a6b8d63186da8643c1aeb17ca54cce1193d7b0ebc57bc6bd  dwarfs-0.15.7.tar.xz
    42a22273df41026bf6eb6fd6001797213388430cae5a4b1b08dfaebacf5b1d61  dwarfs-0.15.7-Windows-AMD64.7z
    edb698186db6b162c755770ffabfcc05482bb7105a716fd943548e64244b0fd2  dwarfs-fuse-extract-0.15.7-Linux-aarch64
    ad781b78835bc95191209806ff4f05edd240cae9b61e41ce1733516ada4e60a8  dwarfs-fuse-extract-0.15.7-Linux-aarch64.upx
    e9a93c770c47dde882dec6dc678dec94be7526661fc6769c4bbe0f100e0ce8be  dwarfs-fuse-extract-0.15.7-Linux-arm
    5c2d7690de1b3e13a98582e238823aeb550d896ad6e9f24d81c928420b9931cd  dwarfs-fuse-extract-0.15.7-Linux-arm.upx
    d98ea42a790e5cf18ec81c8fd144fe281b88f3cf0e874988df5d40282f6ec323  dwarfs-fuse-extract-0.15.7-Linux-i386
    af45aff62a4871eb38181d83c214c4a1907594e8d2ff3ab3384997f4318e495f  dwarfs-fuse-extract-0.15.7-Linux-i386.upx
    920b5cb8998daab2c4eb1bcc43d9f2f771831835e0574978a8aa90068f84f30c  dwarfs-fuse-extract-0.15.7-Linux-loongarch64
    aa17302ece213eee867421373d602eb05a4c27934441904f5f54e8df79bd3480  dwarfs-fuse-extract-0.15.7-Linux-ppc64
    4454698ed95aa337c72cc840d105eb425b21ad730c18d8faa43bdec94525f1fb  dwarfs-fuse-extract-0.15.7-Linux-ppc64le
    3339208ba117601584dba7d8c2ea270bf7a36112e05a902c9a6cf76eed70920a  dwarfs-fuse-extract-0.15.7-Linux-riscv64
    f813d4edec9cbf0ac8e7362e59bcfa3c4e76e81cc14ef2bc4bf230f32d018509  dwarfs-fuse-extract-0.15.7-Linux-riscv64.upx
    87fb1b22de99fd9db999ac12255da014b25ee916b8f29deb7a2c3cad9eca3e36  dwarfs-fuse-extract-0.15.7-Linux-s390x
    72c106576087e8049f4c669cbf2cec4765201982a035faf65a6ef6dfef3819db  dwarfs-fuse-extract-0.15.7-Linux-x86_64
    ed048eb775aac811f867b19e10a94d2185caca4af61ec5209d3e5911b6853292  dwarfs-fuse-extract-0.15.7-Linux-x86_64.upx
    7e07a81edc6b029470512c6829f61e590734d4a67905675474afd6f273f
    
  3. v0.15.6dwarfs-0.15.62026-07-26 · 2026-07-26 14:33 UTC
    查看更新说明

    Bug fixes

    • Canonicalizing the mount point path in 0.15.4 fixed a bug on Linux, but introduced a new bug on Windows. With canonicalized paths, WinFsp does no longer allow mounting a DwarFS image unless MountUseMountmgrFromFSD is set to 1 in the registry. The fix is to only perform canonicalization on non-Windows platforms. Fixes GitHub issue #375.

    • Ensure UTF-8 safe number formatting on Windows. DwarFS historically uses char and UTF-8 for anything internally. That doesn't play nicely when it comes to stream formatting, in particular numbers, on Windows. The Linux world has workarounds for this in std::numpunct, but Windows does not and will happily return characters that are invalid UTF-8, which will eventually cause the fmt library to fall over. This change checks if the current locale requires number formatting characters that would end up as multi-byte UTF-8 sequences. If so, it will install a "safe" std::numpunct facet as well as a custom fmt::format_facet. The former will simply turn off fancy number formatting, while the latter will override formatting in the fmt library and use UTF-8 strings instead of single byte characters. This was discovered in the log output from GitHub issue #375.

    Other

    Full Changelog: https://github.com/mhx/dwarfs/compare/v0.15.5...v0.15.6

    be22d1ef7e5a044d6f239a6e17d8e203f2226f8381d51c0dd5f2185705f871c3  dwarfs-0.15.6-Linux-aarch64.tar.xz
    d0ecfa777f8cae647abbb8ebedd2e8a13fdd7461aba4046be80572202144b825  dwarfs-0.15.6-Linux-arm.tar.xz
    914561a02ec1b9902b3256426a6b617210286904e20ca0e4c18b3ac9a5eeba27  dwarfs-0.15.6-Linux-i386.tar.xz
    2e049b0d2e6f1adab04e3da3dcf0a17684a464106a16350f1b6c3017602d322e  dwarfs-0.15.6-Linux-loongarch64.tar.xz
    0853633a7097e322b397bb760df9399f6deeb7a269e4d3585d3a4258ebce14f6  dwarfs-0.15.6-Linux-ppc64le.tar.xz
    88f9a7d81c09be3b211e1b5f4b4f589e15fb7c66e561f24848d06ef54f680383  dwarfs-0.15.6-Linux-ppc64.tar.xz
    1474e8ccde21f7d58d73ba641b372c137b17ebc935bb9000b0156e0902577928  dwarfs-0.15.6-Linux-riscv64.tar.xz
    a198b4674d90c77543891ba921dd5a6e164706176bded53b4e44bfd110505dd5  dwarfs-0.15.6-Linux-s390x.tar.xz
    a73d71ca5a0fe0afae4c096e0c6ab61145e825295e7d269f69e46be44a181374  dwarfs-0.15.6-Linux-x86_64.tar.xz
    087b77c1d6a1f253df896b054f95ef17469c63b00be51f4d081633cc8817481c  dwarfs-0.15.6.tar.xz
    3a861a2a35c1031da01a30eaed4d9c8031363253d369e7d0f8f999c1b0edbb06  dwarfs-0.15.6-Windows-AMD64.7z
    c71ef8090dd419857eb7f3ebfe65c32b73c827269c18e3e0efc98318bae4451f  dwarfs-fuse-extract-0.15.6-Linux-aarch64
    709802655dcb9755531956261db0cae4e6e0bcd80b536266b87652a2cd591aa2  dwarfs-fuse-extract-0.15.6-Linux-aarch64.upx
    35867c6b436a14e8375fd7ca5bdab6b723a435c700c47377f5d723bacb14f7b5  dwarfs-fuse-extract-0.15.6-Linux-arm
    a1b95c4d754e8c79ed3e58fe6ca226befae3ca68beb8bce4ed828a7f6950c864  dwarfs-fuse-extract-0.15.6-Linux-arm.upx
    e40e96c490213ee6e8af44a64d41f6929a1510f40c049e46cdf8976e9e4483b1  dwarfs-fuse-extract-0.15.6-Linux-i386
    a3dac48b63dd1e0d9158b4407fdfc233782cedbe733a2898dfc0e1da3e26c048  dwarfs-fuse-extract-0.15.6-Linux-i386.upx
    5dfdae7452c7579bfad39d6505e8f7ce3f37ebe61d8d353343e813a4f0ffeee8  dwarfs-fuse-extract-0.15.6-Linux-loongarch64
    5b23c25260dc49d6ab4278b31b25f0706370b8ad34faf14b6f2546ff0841318a  dwarfs-fuse-extract-0.15.6-Linux-ppc64
    a748502b283f1967c5ed44cbac3c03adda066af506ad0999b32512700013ff3b  dwarfs-fuse-extract-0.15.6-Linux-ppc64le
    c33786ef9b0a3733e4aa79bd8062e535fdfeed83b5332b8d6cc48830718bd2e4  dwarfs-fuse-extract-0.15.6-Linux-riscv64
    da8ea894e7de150473aecf109d5ab0bf4731c5f7d417a030b884e8fe57694ae8  dwarfs-fuse-extract-0.15.6-Linux-riscv64.upx
    06d21fc857b4f7998d880791298675e002a24e744c2ccaac8c
    
  4. v0.15.5dwarfs-0.15.52026-07-11 · 2026-07-11 16:47 UTC
    查看更新说明

    Bug fixes

    • The static release binaries for v0.15.4 were built using a new build container and moving from GCC 15 to GCC 16, a lot of patches were dropped, including a patch that prevented the locale code in libstc++ from throwing exceptions for non "C" locales when using musl-libc. This caused all of the Linux static release binaries to fail to set up the user locale, emit errors on startup, or even die with a runtime error in some cases. The patches were added back into the GCC build. Fixes GitHub issue #371.

    Full Changelog: https://github.com/mhx/dwarfs/compare/v0.15.4...v0.15.5

    8e1c1cc976ac6681d9adad5cca0ed0df572cfe5adbc103f2c18c1a3d19fae1cf  dwarfs-0.15.5-Linux-aarch64.tar.xz
    503463b5b22a7e8afa212bb44e4b8f71c64777fd7c33b038f86a440ef71b2617  dwarfs-0.15.5-Linux-arm.tar.xz
    87671848839fb3fe183abf03c4616eec98f05b74b81d855b03e26071283bdb4a  dwarfs-0.15.5-Linux-i386.tar.xz
    7b2de81e48f24e12eb325e7647b959c26e1a74e3fd208adbb6c960fda3482f43  dwarfs-0.15.5-Linux-loongarch64.tar.xz
    1e6d26f14db7b8f8538cc96d0ff18fd214045d743422e5d7238b33dcf3b8ed7a  dwarfs-0.15.5-Linux-ppc64le.tar.xz
    5da2f0ffacd751f2545d1848d76c96d2b33336aa84ef15c54f1301c7bf99cbed  dwarfs-0.15.5-Linux-ppc64.tar.xz
    bc1a2460da5699cb33b8cf44ac7aef61b1db90a3bef90104955d8bc2f6507dd5  dwarfs-0.15.5-Linux-riscv64.tar.xz
    5b19a1612af9c48c158be36203ce15a4cda9446f1a07a1f6f4f455282bfcf64c  dwarfs-0.15.5-Linux-s390x.tar.xz
    2444fbf7d1c4c0af5417d6c26ab3d0537f43da2fbc721ecce60eae6547ba991b  dwarfs-0.15.5-Linux-x86_64.tar.xz
    e6315f514c2e0db099b7e1627eeed1b968ba316c4a1cb2d96306132303816c00  dwarfs-0.15.5.tar.xz
    5731c5355032495acefe07044cda111bb0d8f12b74c08199b98a7cd64a3c1e55  dwarfs-0.15.5-Windows-AMD64.7z
    6a73e8e1cc3fdc2372606bd42ca7d749e1f1bb8f989d19e793a91ae26fa73d8a  dwarfs-fuse-extract-0.15.5-Linux-aarch64
    b629bea6a8c7b89923bec1073e3102c1bc5daf921bacf116aeb225e2eb6fcc8b  dwarfs-fuse-extract-0.15.5-Linux-aarch64.upx
    495a3e4e3f2a519e5bc963ec77575af0c7274d0417c71de9ee765a6fe3aa1ebc  dwarfs-fuse-extract-0.15.5-Linux-arm
    33b5d9ca60b62904803b3796cbefe2da625a860d91297a37123a2e03532b29d5  dwarfs-fuse-extract-0.15.5-Linux-arm.upx
    6e2da9b568972eb61e2e0a717ac843aa5491e5067345997032ac41637183d2ba  dwarfs-fuse-extract-0.15.5-Linux-i386
    6f1a0c05d4189e5b715df0340662bd027008c3a5ab6afac1f9d62d62db6eaf7c  dwarfs-fuse-extract-0.15.5-Linux-i386.upx
    7e5d5f5020ffdd32e782a24b56596cd0dac87e1f3c06cf35da3f43b6ca9209d8  dwarfs-fuse-extract-0.15.5-Linux-loongarch64
    28805394f00f9d5c52b64ad832f2e59ce94544a61f200647d8d0ecd6d2a82a51  dwarfs-fuse-extract-0.15.5-Linux-ppc64
    fd5db5895cad1ccd8fbfd2a835c74d45a26a1b3faebe2b30559dc871fec66215  dwarfs-fuse-extract-0.15.5-Linux-ppc64le
    134d96071a3fc7a802d42d1bae52ccd728dd79c129910cabe13cfe527f4fddf9  dwarfs-fuse-extract-0.15.5-Linux-riscv64
    b656c8022b6ec98dfb2d55ced4f6775da07b1558de412fc260b3281828df1584  dwarfs-fuse-extract-0.15.5-Linux-riscv64.upx
    4ea71439b8f6b0730964cfaa31ad72d0e45b3d18e3439ce3cac5267cf55b226c  dwarfs-fuse-extract-0.15.5-Linux-s390x
    cc600666d590528122989d26cbfc160e985bb1337a288d9557bfbb7b354bdece  dwarfs-fuse-extract-0.15.5-Linux-x86_64
    c15852c3e7eb190faa118c044ba9cc6c1ff065906e0e909ba038bf3ed16a683a  dwarfs-fuse-extract-0.15.5-Linux-x86_64.upx
    97fea326f32949e254ba659f60507a230584b38a0d53705b03855a1189f93135  dwarfs-universal-0.15.5-Linux-aarch64
    b614590b020bb772f448c4496f6fd227eb4239d9020856602b9bcba2ac2c2361  dwarfs-universal-0.15.5-Linux-aarch64.upx
    4f32f937f03300d40551c064261ceb2c9045e1c90a1e7323f915d11cfa4db1aa  dwarfs-universal-0.15.5-Linux-arm
    5d3ecb099d1b5bde634aa840fb68ced942605592a868b49569df0fa01f914d80  dwarfs-universal-0.15.5-Linux-arm.upx
    d060dd206ed2f0639f5a9882d29a25fc0ec959483d727573a6545062b75a85bc  dwarfs-universal-0.15.5-Linux-i386
    f9aef07e0dd9b2d31217ab3f6f044d05dcf9d0e071a5c1bcc2be13ad0cf5aa79  dwarfs-universal-0.15.5-Linux-i386.upx
    52996090299a602318d7664e29ff220c30db6d057ceb6fb8bea5f01a0455469c  dwarfs-universal-0.15
    
  5. v0.15.4dwarfs-0.15.42026-07-08 · 2026-07-08 07:04 UTC
    查看更新说明

    Bug fixes

    • When using --input-list and directory paths in that list contained trailing slashes, mkdwarfs would create an additional directory with an empty name below that path. When mounting such an image, trying to traverse the file system would result in an I/O error when processing that empty-named directory. This has been fixed by stripping trailing slashes. Another change was made to detect and ignore empty-named directory entries when reading file system images to hide the erroneously created extra directories and allow "buggy" images to be mounted without errors. Fixes GitHub issue #370.

    • When running dwarfs in foreground more, the file system was not properly unmounted when the process was terminated (e.g., with Ctrl+C). This bug was introduced with the FUSE driver refactoring in 0.15.2. This was fixed by canonicalizing the mount point path immediately after parsing.

    • DWARFS_LOG_MEMORY_USAGE was broken due to prematurely closing the log file before anything was logged. This has been fixed.

    • The metadata analysis output from dwarfsck was not consistently using the same order for "plain" and "compact" string table fields. This has been fixed.

    • The debug output for similarity ordering contained a stray closing parenthesis. This has been fixed.

    • A workaround was implemented for a benign race condition in OpenSLL that would occasionally trigger TSAN warnings.

    Build

    • Several dependencies were updated, the static release binaries are now built using Clang 22 with GCC 16's libstdc++.

    Full Changelog: https://github.com/mhx/dwarfs/compare/v0.15.3...v0.15.4

    708ea9d9b20f246e043f8dbb3652272c1465ec1336db7e4881c7acb961e159c8  dwarfs-0.15.4-Linux-aarch64.tar.xz
    e4a6df69fc8afd73ff857168714e0d528a4d0c960b851273b6962c00be488073  dwarfs-0.15.4-Linux-arm.tar.xz
    7f9b2c7f8e0648a0ec5533366c57a679eb836d11c1551484c18fa9f49e60e9cb  dwarfs-0.15.4-Linux-i386.tar.xz
    f20a496fcf3afa335a5d504657df588d06ec8647708cb793f0c87df0dd6599b7  dwarfs-0.15.4-Linux-loongarch64.tar.xz
    43f8cfef1c5632f35e63344c0b292cc9d603a4b70ef8f120e223c22163e14ffa  dwarfs-0.15.4-Linux-ppc64le.tar.xz
    cc1c93976093dae499d1a574c57d851642e5a2b3cb2923ec415e4a8d81118df0  dwarfs-0.15.4-Linux-ppc64.tar.xz
    a10ee68bcdfc2bd5508552315d36032087bbc64330dc8006cda90e4d95802389  dwarfs-0.15.4-Linux-riscv64.tar.xz
    eb30c23c7ab212709e524e6543c5dc82cd6fd72c4286edc4e96855525602c47a  dwarfs-0.15.4-Linux-s390x.tar.xz
    86bcef95c050f21ab88878b2d8f33344698d9b3a3e83ecf30b10fa35225617d1  dwarfs-0.15.4-Linux-x86_64.tar.xz
    454f0e04a1cf6662d6842239ee816ddf3217bd4b8b9e1cc99b3ffb7f20854294  dwarfs-0.15.4.tar.xz
    13a73c6f42c3101607620aa26e1ee9f3fd575eedf9271ef951f96a805de50a10  dwarfs-0.15.4-Windows-AMD64.7z
    3343894450d278eea53403c92445a571358419c733032f1344a7c4132abb2b71  dwarfs-fuse-extract-0.15.4-Linux-aarch64
    87a6bf12dba6e74e4a9c620c1fd2326a513c332712fcac9f4248a5470aff1853  dwarfs-fuse-extract-0.15.4-Linux-aarch64.upx
    01c1e3865ea62a5ad2aea319eb2095e105f0a97e705b4b8aacc7e28f1a9e797b  dwarfs-fuse-extract-0.15.4-Linux-arm
    9f1336c1f63fcf6c1745d73a8e8498585ca87e21c88d3052763d17fcd925274b  dwarfs-fuse-extract-0.15.4-Linux-arm.upx
    730243c5346a1ca5ef443812e3d941001f941ee18c063ffbee9e544e26c92ef2  dwarfs-fuse-extract-0.15.4-Linux-i386
    078084057218656415f3aca65ccb7c5f859f8701f7a7ddce533bc8534c0580ff  dwarfs-fuse-extract-0.15.4-Linux-i386.upx
    43848310c3e9c0a94d4972ed11a3af486d088e653f8edc353775800b881291cd  dwarfs-fuse-extract-0.15.4-Linux-loongarch64
    86c5375e577dfa01596cd118f666b9ed84fae774cf09d5ea6b442bc8842de908  dwarfs-fuse-extract-0.15.4-Linux-ppc64
    7812f2c17f7f738d2f687c05d470dc479e472a2b0e4107dab393962a71306da0  dwarfs-fuse-extract-0.15.4-Linux-ppc64le
    79bb6f67bfbd57697a64e24001625755a3d81a29b0b968c992037575c2e539d9  dwarfs-fuse-extract-0.15.4-Linux-riscv64
    711e0a075b76440dd966c4bde1d8e41612d698b6615e0e793b981b6e6371b794  dwarfs-fuse-extract-0.15.4-Linux-riscv64.upx
    0685
    

用户点评

登录用户的真实使用记录,可按版本和硬件筛选
共 0 条

    还没有人点评。第一个说出真实体验的人最有价值。

    README 原文

    未翻译

    Latest Release Total Downloads Homebrew Downloads DwarFS CI Build Codacy Badge codecov OpenSSF Best Practices Security Rating

    DwarFS

    The Deduplicating Warp-speed Advanced Read-only File System.

    A fast high-compression read-only file system for Linux, FreeBSD, macOS and Windows.

    Table of contents

    What is DwarFS (in plain words)?

    DwarFS is a mountable archive: you pack a directory into one file (an "image"), then open it instantly like a normal folder — no full extraction, no temporary files. It’s read-only when mounted, so you can browse, open, and run files safely in place. This works particularly well for big collections with lots of similar or repeated content — think many versions of a project or dataset, folders of documents and text files, backups/snapshots that mostly overlap, or libraries with many near-duplicates. The image can be mounted in fractions of a second, you can use the contents immediately with no delay, and you extract only if you actually need to.

    Why not just use .zip or .tar.gz?

    Traditional archives are fine for storage, but they’re usually slow to open and awkward for random access (jumping around inside large files or across many files). DwarFS is built for fast random reads and space savings. It groups similar files and removes duplication, so images are often smaller than a simple tar/zip, while reads stay snappy — even when accessing lots of files simultaneously. In practice, you keep huge directories compressed, mount them in milliseconds, and work as if they were already unpacked.

    Performance comparison overview

    This comparison uses DwarFS 0.14.1, 7-Zip 25.00 (x64), and tar + pigz for all .tar.gz tests since plain gzip would have been incredibly slow. Both .tar.gz and 7zip archives were mounted using fuse-archive v1.16 with the -olazycache option since the default of caching the entire archive in memory is infeasible for large archives.

    1139 complete Perl installations

    Perl • Compression ratio Perl • Compression speed Perl • Compression CPU time Perl • Decompression speed Perl • Decompression CPU time Perl • Mount time Perl • Lookup speed Perl • Random access speed

    Perl (47.49 GiB, 1.9M files).tar.gz 1.tar.zst 27zip (-mx=7)SquashFS 3DwarFS (lzma)DwarFS (zstd)
    Compression time4m 59s8m 06s23m 27s5m 37s2m 13s5m 3s
    Compression CPU time1h 47m2h 18m5h 5m2h 29m31m 17s49m 51s
    Compressed size12.17 GiB0.387 GiB1.219 GiB3.245 GiB0.310 GiB0.352 GiB
    Compression ratio3.902122.738.9614.63153.2134.9
    Decompression time2m 19s57.2s1m 14s39.3s48.7s46.5s
    Decompression CPU time3m 44s1m 21s2m 28s1m 25s2m 23s1m 59s
    Mount time2m 07s❌ 43.638s0.011s0.420s0.009s
    Find all 1.9M files 55.670s❌ 45.695s5.311s2.800s2.821s
    Checksum 1139 files (2.58 GiB) 6❌ 7❌ 4~5h 81.541s4.330s1.134s

    All artifacts from 205 DwarFS CI builds

    DwarFS CI • Compression ratio DwarFS CI • Compression speed DwarFS CI • Compression CPU time DwarFS CI • Decompression speed DwarFS CI • Decompression CPU time DwarFS CI • Mount time DwarFS CI • Random access speed

    DwarFS CI (465.2 GiB, 3.6M files).tar.gz (pigz -9)7zip (-mx=7)DwarFS (zstd)
    Compression time40m 28s141m 46s68m 6s
    Compression CPU time13h 55m42h 47m31h 12m
    Compressed size142.9 GiB60.04 GiB28.63 GiB
    Compression ratio3.2557.74816.25
    Decompression time26m 17s8m 15s7m 21s
    Decompression CPU time35m 16s41m 29s8m 59s
    Mount time22m 18s10.5s0.024s
    Run 441 binaries (2.72 GiB) 9❌ 10❌ 110.774s

    Game audio assets from sonniss.com

    Sonniss • Compression ratio Sonniss • Compression speed Sonniss • Compression CPU time Sonniss • Decompression speed Sonniss • Decompression CPU time Sonniss • Mount time Sonniss • Random access speed

    Sonniss (3.072 GiB, 171 files) 12.tar.gz (pigz -9)7zip (-mx=7)SquashFS 13DwarFS (categorize)
    Compression time5.34s6m 21s19.52s3.98s
    Compression CPU time2m 1s19m 14s9m 47s29.0s
    Compressed size2.725 GiB2.255 GiB2.711 GiB1.664 GiB
    Compression ratio1.1271.3621.1331.846
    Decompression time13.7s1m 50s0.774s1.32s
    Decompression CPU time17.8s1m 52s4.60s9.15s
    Mount time13.6s0.014s0.018s0.008s
    Checksum all files 143m 2s30m 42s2.81s2.64s

    Quick Start

    To create a DwarFS image from a directory, use mkdwarfs:

    $ mkdwarfs -i /path/to/input/dir -o /path/to/image.dwarfs
    

    To mount the image, use the dwarfs FUSE driver:

    $ mkdir /path/to/mountpoint
    $ dwarfs /path/to/image.dwarfs /path/to/mountpoint
    $ ls /path/to/mountpoint
    

    To extract the image, use dwarfsextract:

    $ dwarfsextract -i /path/to/image.dwarfs -o /path/to/output/dir
    

    There's also the dwarfsck tool that can be used for a variety of tasks, for example listing the files in a DwarFS image:

    $ dwarfsck /path/to/image.dwarfs -l
    

    It can also be used for generating checksums for each file in the image in a format that is recognized by the *sum commands:

    $ dwarfsck /path/to/image.dwarfs --checksum=sha512 | sha512sum --check
    

    This is useful if you want to verify the integrity of the actual files stored in the DwarFS image.

    Overview

    Windows Screen Capture

    Linux Screen Capture

    DwarFS is a read-only file system with a focus on achieving very high compression ratios, particularly for highly redundant data.

    This probably doesn't sound very exciting, because if it's redundant, it should compress well. However, I found that other read-only, compressed file systems don't do a very good job at making use of this redundancy. See here for a comparison with other compressed file systems.

    DwarFS also doesn't compromise on speed; in my use cases, it performs on par with, or better than, SquashFS. For my primary use case, DwarFS compression is an order of magnitude better than SquashFS compression, it's 6 times faster to build the file system, it's typically faster to access files on DwarFS and it uses less CPU resources.

    To give you an idea of what DwarFS is capable of, here's a quick comparison of DwarFS and SquashFS on a set of video files with a total size of 39 GiB. The twist is that each unique video file has two sibling files with a different set of audio streams (this is an actual use case). So there's redundancy in both the video and audio data, but as the streams are interleaved and identical blocks are typically very far apart, it's challenging to make use of that redundancy for compression. SquashFS essentially fails to compress the source data at all, whereas DwarFS is able to reduce the size to nearly one-third, which is close to the theoretical maximum:

    $ du -hs dwarfs-video-test
    39G     dwarfs-video-test
    $ ls -lh dwarfs-video-test.*fs
    -rw-r--r-- 1 mhx users 14G Jul  2 13:01 dwarfs-video-test.dwarfs
    -rw-r--r-- 1 mhx users 39G Jul 12 09:41 dwarfs-video-test.squashfs
    

    Furthermore, when mounting the SquashFS image and performing a random-read throughput test using fio-3.34, both squashfuse and squashfuse_ll top out at around 230 MiB/s:

    $ fio --readonly --rw=randread --name=randread --bs=64k --direct=1 \
          --opendir=mnt --numjobs=4 --ioengine=libaio --iodepth=32 \
          --group_reporting --runtime=60 --time_based
    [...]
       READ: bw=230MiB/s (241MB/s), 230MiB/s-230MiB/s (241MB/s-241MB/s), io=13.5GiB (14.5GB), run=60004-60004msec
    

    In comparison, DwarFS manages to sustain random read rates of 20 GiB/s:

      READ: bw=20.2GiB/s (21.7GB/s), 20.2GiB/s-20.2GiB/s (21.7GB/s-21.7GB/s), io=1212GiB (1301GB), run=60001-60001msec
    

    Distinct features of DwarFS are:

    • Clustering of files by similarity using a similarity hash function. This makes it easier to exploit the redundancy across file boundaries.

    • Segmentation analysis across file system blocks in order to reduce the size of the uncompressed file system. This saves memory when using the compressed file system and thus potentially allows for higher cache hit rates as more data can be kept in the cache.

    • Categorization framework to categorize files or even fragments of files and then process individual categories differently. For example, this allows you to not waste time trying to compress incompressible files or to compress PCM audio data using FLAC compression.

    • Highly multi-threaded implementation. Both the file system creation tool as well as the FUSE driver are able to make good use of the many cores of your system.

    History

    I started working on DwarFS in 2013 and my main use case and major motivation was that I had several hundred different versions of Perl that were taking up something around 30 gigabytes of disk space, and I was unwilling to spend more than 10% of my hard drive keeping them around for when I happened to need them.

    Up until then, I had been using Cromfs for squeezing them into a manageable size. However, I was getting more and more annoyed by the time it took to build the file system image and, to make things worse, more often than not it was crashing after about an hour or so.

    I had obviously also looked into SquashFS, but never got anywhere close to the compression rates of Cromfs.

    This alone wouldn't have been enough to get me into writing DwarFS, but at around the same time, I was pretty obsessed with the recent developments and features of newer C++ standards and really wanted a C++ hobby project to work on. Also, I've wanted to do something with FUSE for quite some time. Last but not least, I had been thinking about the problem of compressed file systems for a bit and had some ideas that I definitely wanted to try.

    A lot of the initial code was written in 2013, then I did a couple of cleanups, bugfixes and refactors every once in a while, but I never really got it to a state where I would feel happy releasing it. It was too awkward to build with its dependency on Facebook's folly library and it didn't have any documentation.

    Digging out the project again in 2020, things didn't look as grim as they used to. Folly was building with CMake and so I just pulled it in as a submodule. Most other dependencies could be satisfied from packages that should be widely available.

    However, the folly and fbthrift dependencies were still a bit of a pain. DwarFS used only a small fraction of folly, and an even tinier fraction of fbthrift. Folly had so many dependencies that it was getting harder and harder to build. Often, when upgrading, there were new build problems. So in 2026, after some of my pull requests for folly and fbthrift had been stuck in review with no feedback for more than half a year, I decided to ditch folly, fork the bits I needed from fbthrift, and re-write the thrift code generation in Python. That was quite a bit of work, but less than I anticipated. A lot of the folly features could be replaced by Boost or the C++ standard library. Some other, more unique features had to be re-implemented. This, however, gave me the freedom to just fix the things I previously had pull requests for, and it turned out that the new implementation was measurably faster in some cases. Also, some re-implementations were kept much simpler than the original folly code, making the resulting binaries smaller. A bunch of dependencies, such as gflags, glog, libevent, double-conversion, and fast_float, could be removed entirely.

    Building and Installing

    Note to Package Maintainers

    DwarFS should usually build fine with minimal changes out of the box. If it doesn't, please file an issue. I've set up CI jobs using Docker images for Ubuntu (22.04 and 24.04), Fedora Rawhide, openSUSE Tumbleweed, Arch Linux, and Debian, as well as a setup script for FreeBSD, that can help with determining an up-to-date set of dependencies. Note that building from the release tarball requires less dependencies than building from the git repository, notably the ronn tool as well as Python and the mistletoe Python module are not required when building from the release tarball.

    There are some things to be aware of:

    • While supported by the CMakeLists.txt via -DPREFER_SYSTEM_GTEST=ON, it is not recommended to use a system-installed version of GoogleTest. GoogleTest itself recommends downloading it as part of the build, and issues like this one can arise quickly if the system-installed version is built with different compiler flags than the DwarFS build.

    • For other bundled libraries (namely fmt, parallel-hashmap, range-v3), the system-installed version is used as long as it meets the minimum required version. Otherwise, the preferred version is fetched during the build.

    Prebuilt Binaries

    Each release has pre-built, statically linked binaries for a number of architectures available for download. These should run without any dependencies and can be useful especially on older distributions where you can't easily build the tools from source. For example, these binaries will work perfectly fine on Linux distributions from 2007.

    Universal Binaries

    In addition to the binary tarballs, there's a universal binary available for each architecture. These universal binaries contain all tools (mkdwarfs, dwarfsck, dwarfsextract and the dwarfs fuse3 driver) in a single executable. These executables are self-extracting and use either a custom self-extractor or upx. They are generally optimized to provide the best performance in the smallest possible binary and are much smaller than the individual tools combined. However, this also means the binaries need to be decompressed each time they are run, which can add significant overhead. If that is an issue, you can either stick to the "classic" individual binaries or you can decompress the universal binary. For upx-compressed binaries, you can use:

    $ upx -d dwarfs-universal-0.7.0-Linux-aarch64
    

    For the binaries that use the custom self-extractor, you can use:

    $ ./dwarfs-universal-riscv64 --extract-wrapped-binary dwarfs-universal
    

    Note that both self-extractors need at least Linux kernel 3.17 to work properly. If you want to use the FUSE driver, you'll need to install the fuse3 tools for your distribution. If you want to run the binaries on an older kernel, you can unpack the universal binary (unpacking does not require kernel 3.17). If you're stuck with fuse2, you must use the individual dwarfs2 driver instead of the universal binary.

    You can run the universal binaries via symbolic links named after the tool. For example:

    $ ln -s dwarfs-universal-0.7.0-Linux-aarch64 mkdwarfs
    $ ./mkdwarfs --help
    

    This also works on Windows if the file system supports symbolic links:

    > mklink mkdwarfs.exe dwarfs-universal-0.7.0-Windows-AMD64.exe
    > .\mkdwarfs.exe --help
    

    Alternatively, you can select the tool by passing --tool=<name> as the first argument on the command line:

    > .\dwarfs-universal-0.7.0-Windows-AMD64.exe --tool=mkdwarfs --help
    

    Note that just like the dwarfs.exe Windows binary, the universal Windows binary depends on the winfsp-x64.dll from the WinFsp project. However, for the universal binary, the DLL is loaded lazily, so you can still use all other tools without the DLL. See the Windows Support section for more details.

    Dependencies

    DwarFS uses CMake as a build tool.

    If you build from a release tarball, the build will be faster and there will be fewer dependencies, because the release tarball ships with the auto-generated files that would otherwise have to be generated during the in-repository build.

    You can grab an up-to-date list of dependencies from the docker files used for the CI builds:

    For FreeBSD, you can use PKGS from the script that sets up the jail for the CI builds.

    Building

    First, unpack the release tarball:

    $ tar xvf dwarfs-x.y.z.tar.xz
    $ cd dwarfs-x.y.z
    

    Alternatively, you can also clone the git repository, but be aware that this has more dependencies and the build will likely take longer because the release archive ships with most of the auto-generated files that will have to be generated when building from the repository:

    $ git clone --recurse-submodules https://github.com/mhx/dwarfs
    $ cd dwarfs
    

    Once all dependencies have been installed, you can build DwarFS using:

    $ mkdir build
    $ cd build
    $ cmake .. -GNinja -DWITH_TESTS=ON
    $ ninja
    

    You can then run tests with:

    $ ctest -j
    

    All binaries use jemalloc as a memory allocator by default, as it typically uses much less system memory compared to the glibc or tcmalloc allocators. To disable the use of jemalloc, pass -DUSE_JEMALLOC=0 on the cmake command line.

    It is also possible to build/install the DwarFS libraries, tools, and FUSE driver independently. This is mostly interesting when packaging DwarFS. Note that the tools and FUSE driver require the libraries to be either built or already installed. To build just the libraries, use:

    $ cmake .. -GNinja -DWITH_TESTS=ON -DWITH_LIBDWARFS=ON -DWITH_TOOLS=OFF -DWITH_FUSE_DRIVER=OFF
    

    Once the libraries are tested and installed, you can build the tools (i.e. mkdwarfs, dwarfsck, dwarfsextract) using:

    $ cmake .. -GNinja -DWITH_TESTS=ON -DWITH_LIBDWARFS=OFF -DWITH_TOOLS=ON -DWITH_FUSE_DRIVER=OFF
    

    To build the FUSE driver, use:

    $ cmake .. -GNinja -DWITH_TESTS=ON -DWITH_LIBDWARFS=OFF -DWITH_TOOLS=OFF -DWITH_FUSE_DRIVER=ON
    

    Installing

    Installing is as easy as:

    $ sudo ninja install
    

    Though you don't have to install the tools to play with them.

    Static Builds

    Attempting to build statically linked binaries is highly discouraged and not officially supported. That being said, here's how to set up an environment where you might be able to build static binaries.

    This has been tested with ubuntu-22.04-live-server-amd64.iso. First, install all the packages listed as dependencies above. Also install:

    $ apt install ccache ninja libacl1-dev
    

    ccache and ninja are optional, but help with a speedy compile.

    Depending on your distribution, you'll need to build and install static versions of some libraries, e.g. libarchive and libmagic for Ubuntu:

    $ wget https://github.com/libarchive/libarchive/releases/download/v3.6.2/libarchive-3.6.2.tar.xz
    $ tar xf libarchive-3.6.2.tar.xz && cd libarchive-3.6.2
    $ ./configure --prefix=/opt/static-libs --without-iconv --without-xml2 --without-expat
    $ make && sudo make install
    
    $ wget ftp://ftp.astron.com/pub/file/file-5.44.tar.gz
    $ tar xf file-5.44.tar.gz && cd file-5.44
    $ ./configure --prefix=/opt/static-libs --enable-static=yes --enable-shared=no
    $ make && make install
    

    That's it! Now you can try building static binaries for DwarFS:

    $ git clone --recurse-submodules https://github.com/mhx/dwarfs
    $ cd dwarfs && mkdir build && cd build
    $ cmake .. -GNinja -DWITH_TESTS=ON -DSTATIC_BUILD_DO_NOT_USE=ON \
               -DSTATIC_BUILD_EXTRA_PREFIX=/opt/static-libs
    $ ninja
    $ ninja test
    

    Usage

    Please check out the manual pages for mkdwarfs, dwarfs, dwarfsck and dwarfsextract. You can also access the manual pages using the --man option to each binary, e.g.:

    $ mkdwarfs --man
    

    The dwarfs manual page also shows an example for setting up DwarFS with overlayfs in order to create a writable file system mount on top of a read-only DwarFS image.

    A description of the DwarFS file system format can be found in dwarfs-format(5).

    A high-level overview of the internal operation of mkdwarfs is shown in this sequence diagram.

    Using the Libraries

    Using the DwarFS libraries should be pretty straightforward if you're using CMake to build your project. For a quick start, have a look at the example code that uses the libraries to print information about a DwarFS image (like dwarfsck) or extract it (like dwarfsextract).

    There are five individual libraries:

    • dwarfs_common contains the common code required by all the other libraries. The interfaces are defined in dwarfs/.

    • dwarfs_reader contains all code required to read data from a DwarFS image. The interfaces are defined in dwarfs/reader/.

    • dwarfs_extractor contains the code required to extract a DwarFS image using libarchive. The interfaces are defined in dwarfs/utility/filesystem_extractor.h.

    • dwarfs_writer contains the code required to create DwarFS images. The interfaces are defined in dwarfs/writer/.

    • dwarfs_rewrite contains the code to re-write DwarFS images. The interfaces are defined in dwarfs/utility/rewrite_filesystem.h.

    The headers in internal subfolders are only accessible at build time and won't be installed. The same goes for the tool subfolder.

    The reader and extractor APIs should be fairly stable. The writer APIs are likely going to change. Note, however, that there are no guarantees on API stability before this project reaches version 1.0.0.

    Windows Support

    Support for the Windows operating system is currently experimental. Having worked pretty much exclusively in a Unix world for the past two decades, my experience with Windows development is rather limited and I'd expect there to definitely be bugs and rough edges in the Windows code.

    The Windows version of the DwarFS file system driver relies on the awesome WinFsp project and its winfsp-x64.dll must be discoverable by the dwarfs.exe driver.

    The different tools should behave pretty much the same whether you're using them on Linux or Windows. The file system images can be copied between Linux and Windows and images created on one OS should work fine on the other.

    There are a few things worth pointing out, though:

    • Windows traditionally treats file names as case-insensitive. However, the DwarFS driver is case-sensitive by default, even on Windows. There's an option -o case_insensitive that can be passed to the driver to make it behave like a traditional Windows file system. Note that this is only supported if the DwarFS image contains no directories or files with the same name differing only in case within the same directory. Otherwise, the driver will emit a warning when mounting the image.

    • DwarFS supports both hardlinks and symlinks on Windows, just as it does on Linux. However, creating hardlinks and symlinks seems to require admin privileges on Windows, so if, for example, you want to extract a DwarFS image that contains links of some sort, you might run into errors if you don't have the right privileges.

    • Due to a problem in WinFsp, symlinks cannot currently point outside of the mounted file system. Furthermore, due to another problem in WinFsp, symlinks with a drive letter will appear with a mangled target path.

    • The DwarFS driver on Windows correctly reports hardlink counts via its API, but currently these counts are not correctly propagated to the Windows file system layer. This is presumably due to a problem in WinFsp.

    • When mounting a DwarFS image on Windows, the mount point must not exist. This is different from Linux, where the mount point must actually exist. Also, it's possible to mount a DwarFS image as a drive letter, e.g.

      dwarfs.exe image.dwarfs Z:

    • Filter rules for mkdwarfs always require Unix path separators, regardless of whether it's running on Windows or Linux.

    Building on Windows

    Building on Windows is not too complicated thanks to vcpkg. You'll need to install:

    WinFsp is expected to be installed in C:\Program Files (x86)\WinFsp; if it's not, you'll need to set WINFSP_PATH when running CMake via cmake/win.bat.

    Clone vcpkg and dwarfs:

    > cd %HOMEPATH%
    > mkdir git
    > cd git
    > git clone https://github.com/Microsoft/vcpkg.git
    > git clone https://github.com/mhx/dwarfs
    

    Then, bootstrap vcpkg:

    > .\vcpkg\bootstrap-vcpkg.bat
    

    And build DwarFS:

    > cd dwarfs
    > mkdir build
    > cd build
    > ..\cmake\win.bat
    > ninja
    

    Once that's done, you should be able to run the tests. Set CTEST_PARALLEL_LEVEL according to the number of CPU cores in your machine.

    > set CTEST_PARALLEL_LEVEL=10
    > ninja test
    

    macOS Support

    The DwarFS libraries and tools (mkdwarfs, dwarfsck, dwarfsextract) are now available from Homebrew:

    $ brew install dwarfs
    $ brew test dwarfs
    

    The macOS version of the DwarFS file system driver relies on the awesome macFUSE project and is available via gromgit's homebrew-fuse tap:

    $ brew tap gromgit/homebrew-fuse
    $ brew install dwarfs-fuse-mac
    

    Occasionally, when the main Homebrew repository switches to newer library versions, the bottles in the homebrew-fuse tap can break. In that case, you will see the FUSE driver fail to run with errors about missing libraries from dyld. If that happens, you can file an issue for the homebrew-fuse tap. You can also temporarily work around the problem by building the FUSE driver from source:

    $ brew reinstall --build-from-source dwarfs-fuse-mac
    

    Use Cases

    Astrophotography

    Astrophotography can generate huge amounts of raw image data. During a single night, it's not unlikely to end up with a few dozen gigabytes of data. With most dedicated astrophotography cameras, this data ends up in the form of FITS images. These are usually uncompressed, don't compress very well with standard compression algorithms, and while there are certain compressed FITS formats, these aren't widely supported.

    One of the compression formats (simply called "Rice") compresses reasonably well and is really fast. However, its implementation for compressed FITS has a few drawbacks. The most severe drawbacks are that compression isn't quite as good as it could be for color sensors and sensors with a less than 16 bits of resolution.

    DwarFS supports the ricepp (Rice++) compression, which builds on the basic idea of Rice compression, but makes a few enhancements: it compresses color and low bit depth images significantly better and always searches for the optimum solution during compression instead of relying on a heuristic.

    Let's look at an example using 129 images (darks, flats and lights) taken with an ASI1600MM camera. Each image is 32 MiB, so a total of 4 GiB of data. Compressing these with the standard fpack tool takes about 16.6 seconds and yields a total output size of 2.2 GiB:

    $ time fpack */*.fit */*/*.fit
    
    user    14.992
    system  1.592
    total   16.616
    
    $ find . -name '*.fz' -print0 | xargs -0 cat | wc -c
    2369943360
    

    However, this leaves you with *.fz files that not every application can actually read.

    Using DwarFS, here's what we get:

    $ mkdwarfs -i ASI1600 -o asi1600-20.dwarfs -S 20 --categorize
    I 08:47:47.459077 scanning "ASI1600"
    I 08:47:47.491492 assigning directory and link inodes...
    I 08:47:47.491560 waiting for background scanners...
    I 08:47:47.675241 scanning CPU time: 1.051s
    I 08:47:47.675271 finalizing file inodes...
    I 08:47:47.675330 saved 0 B / 3.941 GiB in 0/258 duplicate files
    I 08:47:47.675360 assigning device inodes...
    I 08:47:47.675371 assigning pipe/socket inodes...
    I 08:47:47.675381 building metadata...
    I 08:47:47.675393 building blocks...
    I 08:47:47.675398 saving names and symlinks...
    I 08:47:47.675514 updating name and link indices...
    I 08:47:47.675796 waiting for segmenting/blockifying to finish...
    I 08:47:50.274285 total ordering CPU time: 616.3us
    I 08:47:50.274329 total segmenting CPU time: 1.132s
    I 08:47:50.279476 saving chunks...
    I 08:47:50.279622 saving directories...
    I 08:47:50.279674 saving shared files table...
    I 08:47:50.280745 saving names table... [1.047ms]
    I 08:47:50.280768 saving symlinks table... [743ns]
    I 08:47:50.282031 waiting for compression to finish...
    I 08:47:50.823924 compressed 3.941 GiB to 1.201 GiB (ratio=0.304825)
    I 08:47:50.824280 compression CPU time: 17.92s
    I 08:47:50.824316 filesystem created without errors [3.366s]
    ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
    waiting for block compression to finish
    5 dirs, 0/0 soft/hard links, 258/258 files, 0 other
    original size: 3.941 GiB, hashed: 315.4 KiB (18 files, 0 B/s)
    scanned: 3.941 GiB (258 files, 117.1 GiB/s), categorizing: 0 B/s
    saved by deduplication: 0 B (0 files), saved by segmenting: 0 B
    filesystem: 3.941 GiB in 4037 blocks (4550 chunks, 516/516 fragments, 258 inodes)
    compressed filesystem: 4037 blocks/1.201 GiB written
    

    In less than 3.4 seconds, it compresses the data down to 1.2 GiB, almost half the size of the fpack output.

    In addition to saving a lot of disk space, this can also be useful when your data is stored on a NAS. Here's a comparison of the same set of data accessed over a 1 Gb/s network connection, first using the uncompressed raw data:

    find /mnt/ASI1600 -name '*.fit' -print0 | xargs -0 -P4 -n1 cat | dd of=/dev/null status=progress
    4229012160 bytes (4.2 GB, 3.9 GiB) copied, 36.0455 s, 117 MB/s
    

    And next using a DwarFS image on the same share:

    $ dwarfs /mnt/asi1600-20.dwarfs mnt
    
    $ find mnt -name '*.fit' -print0 | xargs -0 -P4 -n1 cat | dd of=/dev/null status=progress
    4229012160 bytes (4.2 GB, 3.9 GiB) copied, 14.3681 s, 294 MB/s
    

    That's roughly 2.5 times faster. You can very likely see similar results with slow external hard drives.

    Dealing with Bit Rot

    Currently, DwarFS has no built-in ability to add recovery information to a file system image. However, for archival purposes, it's a good idea to have such recovery information in order to be able to repair a damaged image.

    This is fortunately relatively straightforward using something like par2cmdline:

    $ par2create -n1 asi1600-20.dwarfs
    

    This will create two additional files that you can place alongside the image (or on a different storage), as you'll only need them if DwarFS has detected an issue with the file system image. If there's an issue, you can run

    $ par2repair asi1600-20.dwarfs
    

    which will very likely be able to recover the image if less than 5% (that's the default used by par2create) of the image are damaged.

    Extended Attributes

    Preserving Extended Attributes in DwarFS Images

    Extended attributes are not currently supported. Any extended attributes stored in the source file system will not currently be preserved when building a DwarFS image using mkdwarfs.

    Extended Attributes exposed by the FUSE Driver

    That being said, the root inode of a mounted DwarFS image currently exposes one or two extended attributes on Linux:

    $ attr -l mnt
    Attribute "dwarfs.driver.pid" has a 4 byte value for mnt
    Attribute "dwarfs.driver.perfmon" has a 4849 byte value for mnt
    

    The dwarfs.driver.pid attribute simply contains the PID of the DwarFS FUSE driver. The dwarfs.driver.perfmon attribute contains the current results of the performance monitor.

    Furthermore, each regular file exposes an attribute dwarfs.inodeinfo with information about the underlying inode:

    $ attr -l "05 Disappear.caf"
    Attribute "dwarfs.inodeinfo" has a 448 byte value for 05 Disappear.caf
    

    The attribute contains a JSON object with information about the underlying inode:

    $ attr -qg dwarfs.inodeinfo "05 Disappear.caf"
    {
      "chunks": [
        {
          "block": 2,
          "category": "pcmaudio/metadata",
          "offset": 270976,
          "size": 4096
        },
        {
          "block": 414,
          "category": "pcmaudio/waveform",
          "offset": 37594368,
          "size": 29514492
        },
        {
          "block": 419,
          "category": "pcmaudio/waveform",
          "offset": 0,
          "size": 29385468
        }
      ],
      "gid": 100,
      "mode": 33188,
      "modestring": "----rw-r--r--",
      "uid": 1000
    }
    

    This is useful, for example, to check how a particular file is spread across multiple blocks or which categories have been assigned to the file.

    Comparison

    The SquashFS, xz, lrzip, zpaq and wimlib tests were all done on an 8 core Intel(R) Xeon(R) E-2286M CPU @ 2.40GHz with 64 GiB of RAM.

    The Cromfs tests were done with an older version of DwarFS on a 6 core Intel(R) Xeon(R) CPU D-1528 @ 1.90GHz with 64 GiB of RAM.

    The EROFS tests were done using DwarFS v0.9.8 and EROFS v1.7.1 on an Intel(R) Core(TM) i9-13900K with 64 GiB of RAM.

    The systems were mostly idle during all of the tests.

    With SquashFS

    The source directory contained 1139 different Perl installations from 284 distinct releases, a total of 47.65 GiB of data in 1,927,501 files and 330,733 directories. The source directory was freshly unpacked from a tar archive to an XFS partition on a 970 EVO Plus 2TB NVMe drive, so most of its contents were likely cached.

    I'm using the same compression type and compression level for SquashFS that is the default setting for DwarFS:

    $ time mksquashfs install perl-install.squashfs -comp zstd -Xcompression-level 22
    Parallel mksquashfs: Using 16 processors
    Creating 4.0 filesystem on perl-install-zstd.squashfs, block size 131072.
    [=========================================================/] 2107401/2107401 100%
    
    Exportable Squashfs 4.0 filesystem, zstd compressed, data block size 131072
            compressed data, compressed metadata, compressed fragments,
            compressed xattrs, compressed ids
            duplicates are removed
    Filesystem size 4637597.63 Kbytes (4528.90 Mbytes)
            9.29% of uncompressed filesystem size (49922299.04 Kbytes)
    Inode table size 19100802 bytes (18653.13 Kbytes)
            26.06% of uncompressed inode table size (73307702 bytes)
    Directory table size 19128340 bytes (18680.02 Kbytes)
            46.28% of uncompressed directory table size (41335540 bytes)
    Number of duplicate files found 1780387
    Number of inodes 2255794
    Number of files 1925061
    Number of fragments 28713
    Number of symbolic links  0
    Number of device nodes 0
    Number of fifo nodes 0
    Number of socket nodes 0
    Number of directories 330733
    Number of ids (unique uids + gids) 2
    Number of uids 1
            mhx (1000)
    Number of gids 1
            users (100)
    
    real    32m54.713s
    user    501m46.382s
    sys     0m58.528s
    

    For DwarFS, I'm sticking to the defaults:

    $ time mkdwarfs -i install -o perl-install.dwarfs
    I 11:33:33.310931 scanning install
    I 11:33:39.026712 waiting for background scanners...
    I 11:33:50.681305 assigning directory and link inodes...
    I 11:33:50.888441 finding duplicate files...
    I 11:34:01.120800 saved 28.2 GiB / 47.65 GiB in 1782826/1927501 duplicate files
    I 11:34:01.122608 waiting for inode scanners...
    I 11:34:12.839065 assigning device inodes...
    I 11:34:12.875520 assigning pipe/socket inodes...
    I 11:34:12.910431 building metadata...
    I 11:34:12.910524 building blocks...
    I 11:34:12.910594 saving names and links...
    I 11:34:12.910691 bloom filter size: 32 KiB
    I 11:34:12.910760 ordering 144675 inodes using nilsimsa similarity...
    I 11:34:12.915555 nilsimsa: depth=20000 (1000), limit=255
    I 11:34:13.052525 updating name and link indices...
    I 11:34:13.276233 pre-sorted index (660176 name, 366179 path lookups) [360.6ms]
    I 11:35:44.039375 144675 inodes ordered [91.13s]
    I 11:35:44.041427 waiting for segmenting/blockifying to finish...
    I 11:37:38.823902 bloom filter reject rate: 96.017% (TPR=0.244%, lookups=4740563665)
    I 11:37:38.823963 segmentation matches: good=454708, bad=6819, total=464247
    I 11:37:38.824005 segmentation collisions: L1=0.008%, L2=0.000% [2233254 hashes]
    I 11:37:38.824038 saving chunks...
    I 11:37:38.860939 saving directories...
    I 11:37:41.318747 waiting for compression to finish...
    I 11:38:56.046809 compressed 47.65 GiB to 430.9 MiB (ratio=0.00883101)
    I 11:38:56.304922 filesystem created without errors [323s]
    ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
    waiting for block compression to finish
    330733 dirs, 0/2440 soft/hard links, 1927501/1927501 files, 0 other
    original size: 47.65 GiB, dedupe: 28.2 GiB (1782826 files), segment: 15.19 GiB
    filesystem: 4.261 GiB in 273 blocks (319178 chunks, 144675/144675 inodes)
    compressed filesystem: 273 blocks/430.9 MiB written [depth: 20000]
    █████████████████████████████████████████████████████████████████████████████▏100% |
    
    real    5m23.030s
    user    78m7.554s
    sys     1m47.968s
    

    So in this comparison, mkdwarfs is more than 6 times faster than mksquashfs, both in terms of CPU time and wall clock time.

    $ ll perl-install.*fs
    -rw-r--r-- 1 mhx users  447230618 Mar  3 20:28 perl-install.dwarfs
    -rw-r--r-- 1 mhx users 4748902400 Mar  3 20:10 perl-install.squashfs
    

    In terms of compression ratio, the DwarFS file system is more than 10 times smaller than the SquashFS file system. With DwarFS, the content has been compressed down to less than 0.9% (!) of its original size. This compression ratio only considers the data stored in the individual files, not the actual disk space used. On the original XFS file system, according to du, the source folder uses 52 GiB, so the DwarFS image actually only uses 0.8% of the original space.

    Here's another comparison using lzma compression instead of zstd:

    $ time mksquashfs install perl-install-lzma.squashfs -comp lzma
    
    real    13m42.825s
    user    205m40.851s
    sys     3m29.088s
    
    $ time mkdwarfs -i install -o perl-install-lzma.dwarfs -l9
    
    real    3m43.937s
    user    49m45.295s
    sys     1m44.550s
    
    $ ll perl-install-lzma.*fs
    -rw-r--r-- 1 mhx users  315482627 Mar  3 21:23 perl-install-lzma.dwarfs
    -rw-r--r-- 1 mhx users 3838406656 Mar  3 20:50 perl-install-lzma.squashfs
    

    It's immediately obvious that the runs are significantly faster and the resulting images are significantly smaller. Still, mkdwarfs is about 4 times faster and produces and image that's 12 times smaller than the SquashFS image. The DwarFS image is only 0.6% of the original file size.

    So, why not use lzma instead of zstd by default? The reason is that lzma is about an order of magnitude slower to decompress than zstd. If you're only accessing data on your compressed file system occasionally, this might not be a big deal, but if you use it extensively, zstd will result in better performance.

    The comparisons above are not completely fair. mksquashfs by default uses a block size of 128KiB, whereas mkdwarfs uses 16MiB blocks by default, or even 64MiB blocks with -l9. When using identical block sizes for both file systems, the difference, quite expectedly, becomes a lot less dramatic:

    $ time mksquashfs install perl-install-lzma-1M.squashfs -comp lzma -b 1M
    
    real    15m43.319s
    user    139m24.533s
    sys     0m45.132s
    
    $ time mkdwarfs -i install -o perl-install-lzma-1M.dwarfs -l9 -S20 -B3
    
    real    4m25.973s
    user    52m15.100s
    sys     7m41.889s
    
    $ ll perl-install*.*fs
    -rw-r--r-- 1 mhx users  935953866 Mar 13 12:12 perl-install-lzma-1M.dwarfs
    -rw-r--r-- 1 mhx users 3407474688 Mar  3 21:54 perl-install-lzma-1M.squashfs
    

    Even this is still not entirely fair, as it uses a feature (-B3) that allows DwarFS to reference file chunks from up to two previous file system blocks.

    But the point is that this is really where SquashFS tops out, as it doesn't support larger block sizes or back-referencing. And as you'll see below, the larger blocks that DwarFS is using by default don't necessarily negatively impact performance.

    DwarFS also features an option to recompress an existing file system with a different compression algorithm. This can be useful as it allows relatively fast experimentation with different algorithms and options without requiring a full rebuild of the file system. For example, recompressing the above file system with the best possible compression (-l 9):

    $ time mkdwarfs --recompress -i perl-install.dwarfs -o perl-lzma-re.dwarfs -l9
    I 20:28:03.246534 filesystem rewritten without errors [148.3s]
    ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
    filesystem: 4.261 GiB in 273 blocks (0 chunks, 0 inodes)
    compressed filesystem: 273/273 blocks/372.7 MiB written
    ████████████████████████████████████████████████████████████████████▏100% \
    
    real    2m28.279s
    user    37m8.825s
    sys     0m43.256s
    
    $ ll perl-*.dwarfs
    -rw-r--r-- 1 mhx users 447230618 Mar  3 20:28 perl-install.dwarfs
    -rw-r--r-- 1 mhx users 390845518 Mar  4 20:28 perl-lzma-re.dwarfs
    -rw-r--r-- 1 mhx users 315482627 Mar  3 21:23 perl-install-lzma.dwarfs
    

    Note that while the recompressed file system is smaller than the original image, it is still a lot bigger than the file system we previously build with -l9. The reason is that the recompressed image still uses the same block size, and the block size cannot be changed by recompressing.

    In terms of how fast the file system is when using it, a quick test I've done is to freshly mount the file system created above and run each of the 1139 perl executables to print their version.

    $ hyperfine -c "umount mnt" -p "umount mnt; dwarfs perl-install.dwarfs mnt -o cachesize=1g -o workers=4; sleep 1" -P procs 5 20 -D 5 "ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P{procs} sh -c '\$0 -v >/dev/null'"
    Benchmark #1: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P5 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      1.810 s ±  0.013 s    [User: 1.847 s, System: 0.623 s]
      Range (min … max):    1.788 s …  1.825 s    10 runs
    
    Benchmark #2: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      1.333 s ±  0.009 s    [User: 1.993 s, System: 0.656 s]
      Range (min … max):    1.321 s …  1.354 s    10 runs
    
    Benchmark #3: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P15 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      1.181 s ±  0.018 s    [User: 2.086 s, System: 0.712 s]
      Range (min … max):    1.165 s …  1.214 s    10 runs
    
    Benchmark #4: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      1.149 s ±  0.015 s    [User: 2.128 s, System: 0.781 s]
      Range (min … max):    1.136 s …  1.186 s    10 runs
    

    These timings are for initial runs on a freshly mounted file system, running 5, 10, 15 and 20 processes in parallel. 1.1 seconds means that it takes only about 1 millisecond per Perl binary.

    Following are timings for subsequent runs, both on DwarFS (at mnt) and the original XFS (at install). DwarFS is around 15% slower here:

    $ hyperfine -P procs 10 20 -D 10 -w1 "ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P{procs} sh -c '\$0 -v >/dev/null'" "ls -1 install/*/*/bin/perl5* | xargs -d $'\n' -n1 -P{procs} sh -c '\$0 -v >/dev/null'"
    Benchmark #1: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):     347.0 ms ±   7.2 ms    [User: 1.755 s, System: 0.452 s]
      Range (min … max):   341.3 ms … 365.2 ms    10 runs
    
    Benchmark #2: ls -1 install/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):     302.5 ms ±   3.3 ms    [User: 1.656 s, System: 0.377 s]
      Range (min … max):   297.1 ms … 308.7 ms    10 runs
    
    Benchmark #3: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):     342.2 ms ±   4.1 ms    [User: 1.766 s, System: 0.451 s]
      Range (min … max):   336.0 ms … 349.7 ms    10 runs
    
    Benchmark #4: ls -1 install/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):     302.0 ms ±   3.0 ms    [User: 1.659 s, System: 0.374 s]
      Range (min … max):   297.0 ms … 305.4 ms    10 runs
    
    Summary
      'ls -1 install/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null'' ran
        1.00 ± 0.01 times faster than 'ls -1 install/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null''
        1.13 ± 0.02 times faster than 'ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null''
        1.15 ± 0.03 times faster than 'ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null''
    

    Using the lzma-compressed file system, the metrics for initial runs look considerably worse (about an order of magnitude):

    $ hyperfine -c "umount mnt" -p "umount mnt; dwarfs perl-install-lzma.dwarfs mnt -o cachesize=1g -o workers=4; sleep 1" -P procs 5 20 -D 5 "ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P{procs} sh -c '\$0 -v >/dev/null'"
    Benchmark #1: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P5 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):     10.660 s ±  0.057 s    [User: 1.952 s, System: 0.729 s]
      Range (min … max):   10.615 s … 10.811 s    10 runs
    
    Benchmark #2: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P10 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      9.092 s ±  0.021 s    [User: 1.979 s, System: 0.680 s]
      Range (min … max):    9.059 s …  9.126 s    10 runs
    
    Benchmark #3: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P15 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      9.012 s ±  0.188 s    [User: 2.077 s, System: 0.702 s]
      Range (min … max):    8.839 s …  9.277 s    10 runs
    
    Benchmark #4: ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '$0 -v >/dev/null'
      Time (mean ± σ):      9.004 s ±  0.298 s    [User: 2.134 s, System: 0.736 s]
      Range (min … max):    8.611 s …  9.555 s    10 runs
    

    So you might want to consider using zstd instead of lzma if you'd like to optimize for file system performance. It's also the default compression used by mkdwarfs.

    Now here's a comparison with the SquashFS file system:

    $ hyperfine -c 'sudo umount mnt' -p 'umount mnt; dwarfs perl-install.dwarfs mnt -o cachesize=1g -o workers=4; sleep 1' -n dwarfs-zstd "ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '\$0 -v >/dev/null'" -p 'sudo umount mnt; sudo mount -t squashfs perl-install.squashfs mnt; sleep 1' -n squashfs-zstd "ls -1 mnt/*/*/bin/perl5* | xargs -d $'\n' -n1 -P20 sh -c '\$0 -v >/dev/null'"
    Benchmark #1: dwarfs-zstd
      Time (mean ± σ):      1.151 s ±  0.015 s    [User: 2.147 s, System: 0.769 s]
      Range (min … max):    1.118 s …  1.174 s    10 runs
    
    Benchmark #2: squashfs-zstd
      Time (mean ± σ):      6.733 s ±  0.007 s    [User: 3.188 s, System: 17.015 s]
      Range (min … max):    6.721 s …  6.743 s    10 runs
    
    Summary
      'dwarfs-zstd' ran
        5.85 ± 0.08 times faster than 'squashfs-zstd'
    

    So, DwarFS is almost six times faster than SquashFS. But what's more, SquashFS also uses significantly more CPU power. However, the numbers shown above for DwarFS obviously don't include the time spent in the dwarfs process, so I repeated the test outside of hyperfine:

    $ time dwarfs perl-install.dwarfs mnt -o cachesize=1g -o workers=4 -f
    
    real    0m4.569s
    user    0m2.154s
    sys     0m1.846s
    

    So, in total, DwarFS was using 5.7 seconds of CPU time, whereas SquashFS was using 20.2 seconds, almost four times as much. Ignore the 'real' time, this is only how long it took me to unmount the file system again after mounting it.

    Another real-life test was to build and test a Perl module with 624 different Perl versions in the compressed file system. The module I've used, Tie::Hash::Indexed, has an XS component that requires a C compiler to build. So this really accesses a lot of different stuff in the file system:

    • The perl executables and its shared libraries

    • The Perl modules used for writing the Makefile

    • Perl's C header files used for building the module

    • More Perl modules used for running the tests

    I wrote a little script to be able to run multiple builds in parallel:

    #!/bin/bash
    set -eu
    perl=$1
    dir=$(echo "$perl" | cut -d/ --output-delimiter=- -f5,6)
    rsync -a Tie-Hash-Indexed/ $dir/
    cd $dir
    $1 Makefile.PL >/dev/null 2>&1
    make test >/dev/null 2>&1
    cd ..
    rm -rf $dir
    echo $perl
    

    The following command will run up to 16 builds in parallel on the 8 core Xeon CPU, including debug, optimized and threaded versions of all Perl releases between 5.10.0 and 5.33.3, a total of 624 perl installations:

    $ time ls -1 /tmp/perl/install/*/perl-5.??.?/bin/perl5* | sort -t / -k 8 | xargs -d $'\n' -P 16 -n 1 ./build.sh
    

    Tests were done with a cleanly mounted file system to make sure the caches were empty. ccache was primed to make sure all compiler runs could be satisfied from the cache. With SquashFS, the timing was:

    real    0m52.385s
    user    8m10.333s
    sys     4m10.056s
    

    And with DwarFS:

    real    0m50.469s
    user    9m22.597s
    sys     1m18.469s
    

    So, frankly, not much of a difference, with DwarFS being just a bit faster. The dwarfs process itself used:

    real    0m56.686s
    user    0m18.857s
    sys     0m21.058s
    

    So again, DwarFS used less raw CPU power overall, but in terms of wallclock time, the difference is really marginal.

    With SquashFS & xz

    This test uses slightly less pathological input data: the root file system of a recent Raspberry Pi OS release. This file system also contains device inodes, so in order to preserve those, we pass --with-devices to mkdwarfs:

    $ time sudo mkdwarfs -i raspbian -o raspbian.dwarfs --with-devices
    I 21:30:29.812562 scanning raspbian
    I 21:30:29.908984 waiting for background scanners...
    I 21:30:30.217446 assigning directory and link inodes...
    I 21:30:30.221941 finding duplicate files...
    I 21:30:30.288099 saved 31.05 MiB / 1007 MiB in 1617/34582 duplicate files
    I 21:30:30.288143 waiting for inode scanners...
    I 21:30:31.393710 assigning device inodes...
    I 21:30:31.394481 assigning pipe/socket inodes...
    I 21:30:31.395196 building metadata...
    I 21:30:31.395230 building blocks...
    I 21:30:31.395291 saving names and links...
    I 21:30:31.395374 ordering 32965 inodes using nilsimsa similarity...
    I 21:30:31.396254 nilsimsa: depth=20000 (1000), limit=255
    I 21:30:31.407967 pre-sorted index (46431 name, 2206 path lookups) [11.66ms]
    I 21:30:31.410089 updating name and link indices...
    I 21:30:38.178505 32965 inodes ordered [6.783s]
    I 21:30:38.179417 waiting for segmenting/blockifying to finish...
    I 21:31:06.248304 saving chunks...
    I 21:31:06.251998 saving directories...
    I 21:31:06.402559 waiting for compression to finish...
    I 21:31:16.425563 compressed 1007 MiB to 287 MiB (ratio=0.285036)
    I 21:31:16.464772 filesystem created without errors [46.65s]
    ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
    waiting for block compression to finish
    4435 dirs, 5908/0 soft/hard links, 34582/34582 files, 7 other
    original size: 1007 MiB, dedupe: 31.05 MiB (1617 files), segment: 47.23 MiB
    filesystem: 928.4 MiB in 59 blocks (38890 chunks, 32965/32965 inodes)
    compressed filesystem: 59 blocks/287 MiB written [depth: 20000]
    ████████████████████████████████████████████████████████████████████▏100% |
    
    real    0m46.711s
    user    10m39.038s
    sys     0m8.123s
    

    Again, SquashFS uses the same compression options:

    $ time sudo mksquashfs raspbian raspbian.squashfs
    

    Footnotes

    1. using pigz -9 ↩

    2. using zstd --long=31 --ultra -22 -T0 ↩

    3. using -comp zstd -Xcompression-level 22 -b 1M -tailends; using squashfuse_ll 0.6.0 ↩

    4. not supported by fuse-archive ↩ ↩2 ↩3

    5. $ fd -t f . mnt | wc -l ↩

    6. $ ls -1 mnt/*/perl*/bin/perl5* | xargs -d $'\n' -n1 -P16 sha256sum ↩

    7. killed after making no progress for 15 minutes ↩

    8. killed when only 78 files were finished after about 20 minutes ↩

    9. $ ls -1 mnt/*/dwarfs-*-Linux-x86_64-*-minsize*/bin/mkdwarfs | xargs -d $'\n' -n1 -P16 sh -c '$0 -h' ↩

    10. killed after making no progress for 15 minutes ↩

    11. killed after making no progress and consuming more than 32 GiB of RAM ↩

    12. https://hippolytus.feralhosting.com/sonniss/Sonniss.com-GDC2024-GameAudioBundle1of9.zip ↩

    13. using -comp zstd -Xcompression-level 22 -b 1M -tailends ↩

    14. $ find mnt -type f | xargs -d $'\n' -n1 -P16 sha256sum ↩