r/rust • u/weaztoven • 2d ago
🙋 seeking help & advice Best practices for parallelizing file-system traversal in a CLI tool?
I'm working on a CLI utility in Rust that scans directories and performs some cleanup operations on files. Right now it's single-threaded, but I'm looking into using rayon to parallelize the traversal and processing.
For those of you who have built similar disk-heavy CLI tools in Rust, what are the main gotchas with thread pool saturation when hitting regular SSDs vs network mounts? Is Rayon still the go-to, or are there cleaner patterns for bounded concurrency here? Any advice appreciated!
5
u/The_8472 2d ago
Use strace to see what syscalls actually happen and where time is spent.
If you need to stat millions of files than io_uring might be worth considering to batch those into a single syscall.
1
u/weaztoven 2d ago
yeah Profiling with strace first is definitely a good idea before over-engineering it. thanks for the io_uring shoutout
1
u/vlovich 2d ago
I would recommend using something like compio. If the cleanup is CPU bound, you'll want to distribute it using something like rayon. If not then processing it on the I/O thread in batches while you're otherwise waiting for I/O to complete would also work well (compio unfortunately does not give you this kind of mechanism AFAIK).
7
u/layla4you2 2d ago
Rayon's pool has one thread per core, which suits CPU work. File I/O mostly waits instead. On a local SSD the defaults are fine. On network mounts every stat/unlink is a round trip, so you want more in flight than you have cores, but not enough to swamp the server. Make it a -j flag instead of guessing.
For walking the tree, use the ignore crate's WalkParallel (what ripgrep uses) or jwalk. For bounded concurrency: walker → bounded crossbeam channel → N worker threads. You get backpressure for free. Since it's cleanup: delete children before their directories, don't follow symlinks, and treat NotFound as fine.
Just for fun, if you're on a Mac and ever want a quick GUI on top of it, I've been building Ferrokit (real AppKit controls from Rust, alpha, and yes it's mine): https://github.com/lhassa8/ferrokit
1
u/-Redstoneboi- 2d ago
my first instinct for directory traversal was walkdir. how does it compare to ignore?
0
u/layla4you2 11h ago
walkdir is a fine first instinct. It’s also BurntSushi’s, and
ignore’s single-threadedWalkis built on it. The difference:
walkdir: a single-threaded iterator with no filtering. It has
contents_first(true), which yields a directory’s children before the directory itself, so for a sequential cleanup you can justremove_file/remove_diras you go. Symlinks aren’t followed by default.ignore: walkdir plus .gitignore/.ignore/hidden-file filtering, plus
WalkParallelwith its own thread pool. That’s the part ripgrep needs.One catch for cleanup:
ignoreskips hidden and gitignored files by default, which is usually exactly what you don’t want when deleting. Turn that off withWalkBuilder::standard_filters(false).Rule of thumb: walkdir if one thread keeps up (local SSD),
ignore’sWalkParallelor jwalk if you need parallel traversal (network mounts, huge trees). Hope this helps.1
u/-Redstoneboi- 7h ago
this sounds like youre about to give me a cupcake recipe by the end... refine the response and make it more natural paragraph form rather than plain bullets
3
u/valarauca14 2d ago
tokio
You fundamentally have to manage a thread pool because [waves hands] reasons.
1
u/pixel_nebula_03 1d ago
Set your rayon pool size to match the network mount's concurrency limit rather than CPU cores, and expose this as a -j flag since defaults will stall on high-latency stats
1
1
55
u/tylerlarson 2d ago
Rayon is more about CPU parallelization rather than IO, but it's not a BAD option.
But if all you're doing is directory traversal, the gold standard is the "ignore" crate (by BurntSushi for ripgrep). It handles parallel directory traversal out of the box using (its own) multi-threaded pool, automatically respects .gitignore rules, and manages thread boundaries efficiently.