Why disk usage tools are slow on macOS
On a Mac Studio, szt scans 667,000 files in 2.4 seconds, where du takes 17. Here's where the time goes in a disk usage tool, and what szt does differently.
A disk usage tool never reads your files. Sizes live in the file system's records, so the whole job is listing folders and asking how big each entry is. How a tool asks, and how many threads ask at once, turns out to be almost everything.
The benchmark
szt against dua, dust, diskus, gdu, du and ncdu, each with its default settings, on a Mac Studio (M2 Ultra). How it was run is at the end.
- szt2.4 s
- dua2.6 s1.1× slower
- dust8.1 s3.4× slower
- diskus11.4 s4.7× slower
- gdu11.6 s4.8× slower
- du17.2 s7.2× slower
- ncdu17.5 s7.3× slower
Median of five runs per tool. Hover or focus a bar for CPU time and memory.
One call per folder, not one per file
Most tools list a folder's names with readdir and then make a separate call (stat, lstat or fstatat) for every file to learn its size. macOS has a better way: getattrlistbulk returns names, types and sizes for hundreds of entries at once. szt lists every folder that way, so a file never gets a system call of its own.
The per-file calls add up: in a profile of du on the projects folder, 60% of its CPU time goes to fstatat. The system calls each tool's binary imports show who does what:
| Tool | Lists a folder with | Then, for every file |
|---|---|---|
| szt, dua | getattrlistbulk | nothing |
| ncdu | getdirentries | fstatat |
| dust, diskus, gdu | readdir | lstat or fstatat |
| du | fts | fstatat |
dua is the other tool built on getattrlistbulk, which is why it's the close one in the benchmark.
More threads made it slower
With the per-file calls gone, what's left is opening each folder before listing it, and that has a ceiling. Here's szt on the projects folder with different thread counts:
| Threads | Time | Kernel CPU time |
|---|---|---|
| 1 | 12.80 s | 5.1 s |
| 2 | 6.52 s | 5.4 s |
| 4 | 3.27 s | 5.8 s |
| 6 | 2.66 s | 8.5 s |
| 8 | 2.50 s | 11.7 s |
| 10 | 2.70 s | 18.1 s |
| 12 | 3.58 s | 32.4 s |
| 16 | 7.63 s | 106.8 s |
| 24 | 10.59 s | 200.3 s |
A single thread spends much of its time waiting rather than working, so the first few threads help a lot. Up to eight the scan keeps getting faster. Past that it gets slower while the kernel works harder and harder: 24 threads burn 200 seconds of CPU time to finish in 10.6. A profile at 24 threads shows why: 93% of the time is spent inside open, the call that opens a folder so it can be listed. Opening a folder goes through vnode and name-cache bookkeeping that every process on the Mac shares (on this machine the vnode table was full, 263,168 entries out of 263,168, so an open often has to evict an old entry first). Past a handful of threads, they mostly wait for each other.
So szt stops at eight threads. dua's authors arrived at the same number: its thread count defaults to 8 on macOS and to the number of cores elsewhere. Most other tools start a thread per core or more, which on this 24-core Mac means 24 threads for dust and gdu and 72 for diskus. It shows in their CPU time: 122 to 211 seconds for the projects folder, against szt's 12.
Tuned, the per-file calls still cost about double
To tell the two effects apart, here are the other tools at their best thread count on the same Mac:
| Tool | Setting | Projects | Compared with szt |
|---|---|---|---|
| ncdu | -t 8 | 4.24 s | 1.8× |
| dust | -T 8 | 4.33 s | 1.8× |
| diskus | -j 8 | 4.55 s | 1.9× |
| gdu | -m 2 | 5.87 s | 2.4× |
With threads out of the picture they still take 1.8 to 2.4 times as long, and that's the price of asking about every file separately.
Efficiency cores don't help
Apple silicon mixes fast performance cores with slower efficiency cores. macOS runs background-priority threads on efficiency cores only, which makes it possible to try different mixes on one machine:
| Threads | Projects |
|---|---|
| 4 performance threads | 3.17 s |
| 4 performance + 4 efficiency threads | 3.09 s |
| 8 performance threads | 2.36 s |
| 8 efficiency threads | 6.55 s |
| 16 threads on the 8 efficiency cores | 7.15 s |
Four efficiency threads on top of four performance threads barely help (3.09 s against 3.17 s), and more threads than cores is a little slower. So szt starts one thread per performance core, as macOS reports in hw.perflevel0.physicalcpu, up to eight: four on a base M-series chip, and up to eight on the Pro, Max and Ultra.
How the benchmark was run
On a Mac Studio (M2 Ultra, 16 performance cores, 64 GB, macOS 27), each tool with its default settings: one run to warm up, then five timed runs with hyperfine, of which the chart shows the median. The projects folder holds 123 GB in 667,000 files and 112,000 folders;/Applications holds 34 GB in 448,000 files and 60,000 folders.
| Tool | Version | Projects | /Applications | CPU time | Peak memory | Threads |
|---|---|---|---|---|---|---|
| szt | 1.0.0 | 2.4 s | 1.0 s | 12 s | 30 MB | 8 |
| dua | 2.45.1 | 2.6 s | 1.2 s | 14 s | 20 MB | 8 |
| dust | 1.2.6 | 8.1 s | 6.9 s | 122 s | 405 MB | 24 |
| diskus | 0.9.0 | 11.4 s | 7.9 s | 211 s | 64 MB | 72 |
| gdu | 5.38.0 | 11.6 s | 8.6 s | 201 s | 50 MB | 24 |
| du | macOS | 17.2 s | 4.8 s | 9 s | 9 MB | 1 |
| ncdu | 2.9.2 | 17.5 s | 4.8 s | 9 s | 3 MB | 1 |
CPU time (all threads added up) and peak memory are for the projects folder. Every run had the folders' metadata in memory already, as on any second scan; a first scan after a restart reads it from the SSD and takes longer with every tool. Apps such as DaisyDisk and GrandPerspective can't be timed this way and aren't included.
Run it on your Mac
The benchmark is in the szt repo: bench/run.sh times every tool on a folder you pick, and bench/chart.py draws the chart. To run it on your Mac:
brew install urtti/szt/szt dust gdu dua-cli ncdu diskus hyperfinegit clone https://github.com/urtti/szt.git && cd sztbench/run.sh ~/Projectspython3 bench/chart.py bench/results-Projects.json "Scanning ~/Projects" "" docs/benchmarkTry szt
szt is free and open source, for macOS 15 and later. Run it in any folder to get a tree of where the space went.
brew install urtti/szt/szt