Skip to content

Tuning ​

The defaults come from measuring one large application tree. This page shows where the time and memory go, so you can tell which setting to move when your tree or your priorities differ.

Where a first launch spends its time ​

Blender 5.2 is a 1.4 GiB tree of native libraries, Python modules and data files. Packed with the defaults and a hot list, it comes to 453 MiB. Launching it in the background breaks down like this:

LaunchTimeWhat it adds
From a plain folder0.50 sThe application's own start-up.
Again, on a mount that has served it once0.50 sNothing. Every byte and every lookup is already cached by the kernel.
On a fresh mount, after warming has finished0.60 sThe kernel meets each file and directory for the first time, through FUSE.
On a fresh mount, straight away0.73 sWaiting for blocks that are still being decoded.

The last two steps are the whole cost of using an image instead of a folder. Each has its own setting to turn.

First-time lookups cost about 0.1 s here. Every file the application opens, and every file a language runtime probes for and does not find, is a round trip through FUSE the first time. The kernel caches the answers after that. Any FUSE filesystem pays this cost, and no image setting changes it.

Decoding costs the rest. A block cannot be read until it is decoded in full, so the first file in the image waits for the whole first block. At 32 MiB that takes about 40 ms. Warming decodes the hot region with several blocks in flight at once, so the later blocks are usually ready by the time the application needs them.

Choosing a block size ​

PriorityBlock sizeWhy
Smallest image for an application32 MiB, the defaultWithin one percent of 64 MiB, at half the first-block wait.
Smallest image, start-up is irrelevant64 MiBA further 0.25 percent, and twice the decode time per block.
Random access by many readers1 MiB or lessEach read decodes little. A dictionary is trained automatically below 1 MiB.
Small tree, a few tens of megabytes1 to 4 MiBLarge blocks leave the build with few blocks to spread across threads.

The image grows as blocks shrink. On the Blender tree, 16 MiB blocks made an image 1 percent larger than 32 MiB. Very small blocks cost several percent more.

Warming workers ​

More workers decode more of the hot region at once. On a 16-thread machine, raising the limit from 8 to 12 workers took the cold launch from 0.78 s to 0.73 s. Above 12 it got no faster, and each worker holds the block it is decoding, so memory rose instead.

sh
stipple mount app.stipple ~/mnt/app --warm-workers 4

On a machine shared with other work, fewer workers trade a little launch time for less contention. --warm-workers 0 turns warming off. The mount then decodes blocks only when a read needs them, which uses the least memory and suits images that are mostly read in small random pieces.

Memory ​

While the application starts, the mount holds:

  • the decoded metadata, which is small next to the blocks,
  • one block for each worker that is decoding,
  • the block cache, up to its budget, which defaults to eight blocks.

In the Blender test that came to about 437 MiB at peak. Warmed blocks are dropped as soon as their bytes are in the page cache, so the steady state is much lower. The page cache itself belongs to the kernel, which reclaims it under memory pressure like any other cached file.

To use less, lower --warm-workers, set a smaller --cache-budget, or build with smaller blocks. The cache budget must hold at least one block.

Build time and memory ​

Measured on the Python standard library from the quick start, 69 MiB of files, with 4 MiB blocks on 16 threads:

SettingsBuild timePeak memoryImage
--level 19, the default3.3 s769 MiB13.1 MiB
--level 19 --no-bcj1.7 s732 MiB13.3 MiB
--level 90.5 s284 MiB14.4 MiB
--level 30.2 s143 MiB15.8 MiB

The same tree with the default 32 MiB blocks built in 18.3 s, because two blocks kept only two threads busy, and came out at 12.4 MiB. A low level is the quickest way to iterate on a hot list or a layout, with a full build at the end.

--threads scales until every block is in flight. --memory caps memory by holding fewer blocks at once, at the cost of parallelism. Neither --threads nor --memory changes the image, so a build can be tuned to the machine without affecting what it produces.

Measuring your own tree ​

stipple bench times random reads straight through the library, without FUSE, and reports throughput and latency percentiles:

sh
stipple bench app.stipple --reads 2000 --read-size 4096

For start-up, nothing beats timing the real application on a fresh mount. Remount between runs, because a mount that has served the application once keeps everything cached.

Released under the MIT or Apache 2.0 license, at your option.