find: avoid redundant stat per -fstype evaluation

The -fstype matcher called path.symlink_metadata() (a statx/lstat syscall)
on every entry before consulting its cache, just to obtain the device id.
With several -fstype clauses on the same expression (as updatedb builds:
nfs/NFS/proc), every file was stat'd once per clause.

Derive the device id from WalkEntry::metadata() instead, which is cached on
the entry in a OnceCell and shared across all matchers, so each file is
stat'd at most once regardless of how many -fstype clauses run.

On 'updatedb --localpaths=/usr' (~506k files) this drops statx calls from
~1.52M to ~506k and wall-clock from ~1.82s to ~1.32s, with byte-identical
output.
This commit is contained in:
Sylvestre Ledru
2026-06-08 23:38:59 +02:00
parent 5d0e7add34
commit 1be5541c74
+29 -2
View File
@@ -42,8 +42,18 @@ pub fn get_file_system_type(path: &Path, cache: &RefCell<Option<Cache>>) -> URes
Ok(metadata) => metadata,
Err(err) => Err(err)?,
};
let dev_id = metadata.dev().to_string();
fs_type_for_dev(metadata.dev().to_string(), cache)
}
/// Resolve a device id to its filesystem type, consulting (and updating) `cache`.
///
/// Callers are expected to obtain `dev_id` from metadata they already hold (e.g. the cached
/// [`WalkEntry::metadata`]), so that no extra `stat`/`statx` syscall is issued per entry.
///
/// This is only supported on Unix.
#[cfg(unix)]
pub fn fs_type_for_dev(dev_id: String, cache: &RefCell<Option<Cache>>) -> UResult<String> {
if let Some(cache) = cache.borrow().as_ref() {
if cache.dev_id == dev_id {
return Ok(cache.fs_type.clone());
@@ -94,7 +104,24 @@ impl FileSystemMatcher {
impl Matcher for FileSystemMatcher {
#[cfg(unix)]
fn matches(&self, file_info: &WalkEntry, _: &mut MatcherIO) -> bool {
if let Ok(result) = get_file_system_type(file_info.path(), &self.cache) {
use std::os::unix::fs::MetadataExt;
// Reuse the metadata already cached on the entry (a single shared `statx` per entry)
// rather than issuing a fresh `lstat`/`statx` here. With several `-fstype` clauses (as
// `updatedb` builds) this turns N stats per file into one.
let Ok(metadata) = file_info.metadata() else {
writeln!(
&mut stderr(),
"Error getting filesystem type for {}",
file_info.path().to_string_lossy()
)
.unwrap();
return false;
};
let dev_id = metadata.dev().to_string();
if let Ok(result) = fs_type_for_dev(dev_id, &self.cache) {
result == self.fs_text
} else {
writeln!(