From 8ddefc997aa32e9e1214391e4ed07b9ba7a6e469 Mon Sep 17 00:00:00 2001 From: Terts Diepraam Date: Fri, 9 Feb 2024 18:20:00 +0100 Subject: [PATCH] perf: reuse filetype from DirEntry --- src/fs/dir.rs | 19 ++++----- src/fs/file.rs | 100 ++++++++++++++++++++++++++++------------------- src/fs/filter.rs | 2 +- src/main.rs | 1 + 4 files changed, 71 insertions(+), 51 deletions(-) diff --git a/src/fs/dir.rs b/src/fs/dir.rs index 8280f4851..990785139 100644 --- a/src/fs/dir.rs +++ b/src/fs/dir.rs @@ -1,6 +1,7 @@ use crate::fs::feature::git::GitCache; use crate::fs::fields::GitStatus; use std::fs; +use std::fs::DirEntry; use std::io; use std::path::{Path, PathBuf}; use std::slice::Iter as SliceIter; @@ -17,7 +18,7 @@ use crate::fs::File; /// accordingly. (See `File#get_source_files`) pub struct Dir { /// A vector of the files that have been read from this directory. - contents: Vec, + contents: Vec, /// The path that was read. pub path: PathBuf, @@ -35,9 +36,7 @@ impl Dir { pub fn read_dir(path: PathBuf) -> io::Result { info!("Reading directory {:?}", &path); - let contents = fs::read_dir(&path)? - .map(|result| result.map(|entry| entry.path())) - .collect::>()?; + let contents = fs::read_dir(&path)?.collect::, _>>()?; info!("Read directory success {:?}", &path); Ok(Self { contents, path }) @@ -67,7 +66,7 @@ impl Dir { /// Whether this directory contains a file with the given path. pub fn contains(&self, path: &Path) -> bool { - self.contents.iter().any(|p| p.as_path() == path) + self.contents.iter().any(|p| p.path().as_path() == path) } /// Append a path onto the path specified by this directory. @@ -80,7 +79,7 @@ impl Dir { #[allow(clippy::struct_excessive_bools)] pub struct Files<'dir, 'ig> { /// The internal iterator over the paths that have been read already. - inner: SliceIter<'dir, PathBuf>, + inner: SliceIter<'dir, DirEntry>, /// The directory that begat those paths. dir: &'dir Dir, @@ -117,8 +116,9 @@ impl<'dir, 'ig> Files<'dir, 'ig> { /// varies depending on the dotfile visibility flag) fn next_visible_file(&mut self) -> Option, (PathBuf, io::Error)>> { loop { - if let Some(path) = self.inner.next() { - let filename = File::filename(path); + if let Some(entry) = self.inner.next() { + let path = entry.path(); + let filename = File::filename(&path); if !self.dotfiles && filename.starts_with('.') { continue; } @@ -131,7 +131,7 @@ impl<'dir, 'ig> Files<'dir, 'ig> { } if self.git_ignoring { - let git_status = self.git.map(|g| g.get(path, false)).unwrap_or_default(); + let git_status = self.git.map(|g| g.get(&path, false)).unwrap_or_default(); if git_status.unstaged == GitStatus::Ignored { continue; } @@ -143,6 +143,7 @@ impl<'dir, 'ig> Files<'dir, 'ig> { filename, self.deref_links, self.total_size, + entry.file_type().ok(), ) .map_err(|e| (path.clone(), e)); diff --git a/src/fs/file.rs b/src/fs/file.rs index ca0be62b5..4ca1e2c25 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -67,12 +67,15 @@ pub struct File<'dir> { /// path (following a symlink). pub path: PathBuf, + /// The cached filetype for this file + pub filetype: OnceLock, + /// A cached `metadata` (`stat`) call for this file. /// /// This too is queried multiple times, and is *not* cached by the OS, as /// it could easily change between invocations — but exa is so short-lived /// it’s better to just cache it. - pub metadata: std::fs::Metadata, + pub metadata: OnceLock, /// A reference to the directory that contains this file, if any. /// @@ -115,6 +118,7 @@ impl<'dir> File<'dir> { filename: FN, deref_links: bool, total_size: bool, + filetype: Option, ) -> io::Result> where PD: Into>, @@ -124,28 +128,30 @@ impl<'dir> File<'dir> { let name = filename.into().unwrap_or_else(|| File::filename(&path)); let ext = File::ext(&path); - debug!("Statting file {:?}", &path); - let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = false; - let extended_attributes = OnceLock::new(); - let absolute_path = OnceLock::new(); let recursive_size = if total_size { RecursiveSize::Unknown } else { RecursiveSize::None }; + let filetype = match filetype { + Some(f) => OnceLock::from(f), + None => OnceLock::new(), + }; + let mut file = File { name, ext, path, - metadata, parent_dir, is_all_all, deref_links, recursive_size, - extended_attributes, - absolute_path, + filetype, + metadata: OnceLock::new(), + extended_attributes: OnceLock::new(), + absolute_path: OnceLock::new(), }; if total_size { @@ -163,12 +169,8 @@ impl<'dir> File<'dir> { ) -> io::Result> { let ext = File::ext(&path); - debug!("Statting file {:?}", &path); - let metadata = std::fs::symlink_metadata(&path)?; let is_all_all = true; let parent_dir = Some(parent_dir); - let extended_attributes = OnceLock::new(); - let absolute_path = OnceLock::new(); let recursive_size = if total_size { RecursiveSize::Unknown } else { @@ -179,13 +181,14 @@ impl<'dir> File<'dir> { name: name.into(), ext, path, - metadata, parent_dir, is_all_all, deref_links: false, - extended_attributes, - absolute_path, recursive_size, + metadata: OnceLock::new(), + absolute_path: OnceLock::new(), + extended_attributes: OnceLock::new(), + filetype: OnceLock::new(), }; if total_size { @@ -258,6 +261,17 @@ impl<'dir> File<'dir> { } } + fn filetype(&self) -> &std::fs::FileType { + self.filetype.get_or_init(|| self.metadata().file_type()) + } + + pub fn metadata(&self) -> &std::fs::Metadata { + self.metadata.get_or_init(|| { + debug!("Statting file {:?}", &self.path); + std::fs::symlink_metadata(&self.path).unwrap() + }) + } + /// Get the extended attributes of a file path on demand. pub fn extended_attributes(&self) -> &Vec { self.extended_attributes @@ -266,7 +280,7 @@ impl<'dir> File<'dir> { /// Whether this file is a directory on the filesystem. pub fn is_directory(&self) -> bool { - self.metadata.is_dir() + self.filetype().is_dir() } /// Whether this file is a directory, or a symlink pointing to a directory. @@ -299,7 +313,7 @@ impl<'dir> File<'dir> { /// Whether this file is a regular file on the filesystem — that is, not a /// directory, a link, or anything else treated specially. pub fn is_file(&self) -> bool { - self.metadata.is_file() + self.filetype().is_file() } /// Whether this file is both a regular file *and* executable for the @@ -308,36 +322,36 @@ impl<'dir> File<'dir> { #[cfg(unix)] pub fn is_executable_file(&self) -> bool { let bit = modes::USER_EXECUTE; - self.is_file() && (self.metadata.permissions().mode() & bit) == bit + self.is_file() && (self.metadata().permissions().mode() & bit) == bit } /// Whether this file is a symlink on the filesystem. pub fn is_link(&self) -> bool { - self.metadata.file_type().is_symlink() + self.filetype().is_symlink() } /// Whether this file is a named pipe on the filesystem. #[cfg(unix)] pub fn is_pipe(&self) -> bool { - self.metadata.file_type().is_fifo() + self.filetype().is_fifo() } /// Whether this file is a char device on the filesystem. #[cfg(unix)] pub fn is_char_device(&self) -> bool { - self.metadata.file_type().is_char_device() + self.filetype().is_char_device() } /// Whether this file is a block device on the filesystem. #[cfg(unix)] pub fn is_block_device(&self) -> bool { - self.metadata.file_type().is_block_device() + self.filetype().is_block_device() } /// Whether this file is a socket on the filesystem. #[cfg(unix)] pub fn is_socket(&self) -> bool { - self.metadata.file_type().is_socket() + self.filetype().is_socket() } /// Determine the full path resolving all symbolic links on demand. @@ -426,7 +440,8 @@ impl<'dir> File<'dir> { parent_dir: None, path, ext, - metadata, + filetype: OnceLock::from(metadata.file_type()), + metadata: OnceLock::from(metadata), name, is_all_all: false, deref_links: self.deref_links, @@ -473,7 +488,7 @@ impl<'dir> File<'dir> { /// more attentively. #[cfg(unix)] pub fn links(&self) -> f::Links { - let count = self.metadata.nlink(); + let count = self.metadata().nlink(); f::Links { count, @@ -484,7 +499,7 @@ impl<'dir> File<'dir> { /// This file’s inode. #[cfg(unix)] pub fn inode(&self) -> f::Inode { - f::Inode(self.metadata.ino()) + f::Inode(self.metadata().ino()) } /// This actual size the file takes up on disk, in bytes. @@ -503,7 +518,7 @@ impl<'dir> File<'dir> { // Note that metadata.blocks returns the number of blocks // for 512 byte blocks according to the POSIX standard // even though the physical block size may be different. - f::Blocksize::Some(self.metadata.blocks() * 512) + f::Blocksize::Some(self.metadata().blocks() * 512) } else { // directory or symlinks f::Blocksize::None @@ -520,7 +535,7 @@ impl<'dir> File<'dir> { _ => None, }; } - Some(f::User(self.metadata.uid())) + Some(f::User(self.metadata().uid())) } /// The ID of the group that owns this file. @@ -532,7 +547,7 @@ impl<'dir> File<'dir> { _ => None, }; } - Some(f::Group(self.metadata.gid())) + Some(f::Group(self.metadata().gid())) } /// This file’s size, if it’s a regular file. @@ -557,7 +572,7 @@ impl<'dir> File<'dir> { self.recursive_size .map_or(f::Size::None, |bytes, _| f::Size::Some(bytes)) } else if self.is_char_device() || self.is_block_device() { - let device_id = self.metadata.rdev(); + let device_id = self.metadata().rdev(); // MacOS and Linux have different arguments and return types for the // functions major and minor. On Linux the try_into().unwrap() and @@ -571,7 +586,7 @@ impl<'dir> File<'dir> { minor: unsafe { libc::minor(device_id.try_into().unwrap()) } as u32, }) } else if self.is_file() { - f::Size::Some(self.metadata.len()) + f::Size::Some(self.metadata().len()) } else { // symlink f::Size::None @@ -597,7 +612,7 @@ impl<'dir> File<'dir> { #[cfg(unix)] fn recursive_directory_size(&self) -> RecursiveSize { if self.is_directory() { - let key = (self.metadata.dev(), self.metadata.ino()); + let key = (self.metadata().dev(), self.metadata().ino()); if let Some(size) = DIRECTORY_SIZE_CACHE.lock().unwrap().get(&key) { return RecursiveSize::Some(size.0, size.1); } @@ -615,8 +630,8 @@ impl<'dir> File<'dir> { } RecursiveSize::Unknown => {} RecursiveSize::None => { - size += file.metadata.size(); - blocks += file.metadata.blocks(); + size += file.metadata().size(); + blocks += file.metadata().blocks(); } } } @@ -644,7 +659,7 @@ impl<'dir> File<'dir> { /// of a directory when `total_size` is used. #[inline] pub fn length(&self) -> u64 { - self.recursive_size.unwrap_bytes_or(self.metadata.len()) + self.recursive_size.unwrap_bytes_or(self.metadata().len()) } /// Is the file is using recursive size calculation @@ -665,7 +680,7 @@ impl<'dir> File<'dir> { #[cfg(unix)] pub fn is_empty_dir(&self) -> bool { if self.is_directory() { - if self.metadata.nlink() > 2 { + if self.metadata().nlink() > 2 { // Directories will have a link count of two if they do not have any subdirectories. // The '.' entry is a link to itself and the '..' is a link to the parent directory. // A subdirectory will have a link to its parent directory increasing the link count @@ -722,7 +737,7 @@ impl<'dir> File<'dir> { _ => None, }; } - self.metadata + self.metadata() .modified() .map(|st| DateTime::::from(st).naive_utc()) .ok() @@ -737,7 +752,10 @@ impl<'dir> File<'dir> { _ => None, }; } - NaiveDateTime::from_timestamp_opt(self.metadata.ctime(), self.metadata.ctime_nsec() as u32) + NaiveDateTime::from_timestamp_opt( + self.metadata().ctime(), + self.metadata().ctime_nsec() as u32, + ) } #[cfg(windows)] @@ -753,7 +771,7 @@ impl<'dir> File<'dir> { _ => None, }; } - self.metadata + self.metadata() .accessed() .map(|st| DateTime::::from(st).naive_utc()) .ok() @@ -767,7 +785,7 @@ impl<'dir> File<'dir> { _ => None, }; } - match self.metadata.created() { + match self.metadata().created() { Ok(btime) => Some(DateTime::::from(btime).naive_utc()), Err(_) => None, } @@ -822,7 +840,7 @@ impl<'dir> File<'dir> { _ => None, }; } - let bits = self.metadata.mode(); + let bits = self.metadata().mode(); let has_bit = |bit| bits & bit == bit; Some(f::Permissions { diff --git a/src/fs/filter.rs b/src/fs/filter.rs index 162cf0ac0..6a1d72e12 100644 --- a/src/fs/filter.rs +++ b/src/fs/filter.rs @@ -243,7 +243,7 @@ impl SortField { Self::Size => a.length().cmp(&b.length()), #[cfg(unix)] - Self::FileInode => a.metadata.ino().cmp(&b.metadata.ino()), + Self::FileInode => a.metadata().ino().cmp(&b.metadata().ino()), Self::ModifiedDate => a.modified_time().cmp(&b.modified_time()), Self::AccessedDate => a.accessed_time().cmp(&b.accessed_time()), Self::ChangedDate => a.changed_time().cmp(&b.changed_time()), diff --git a/src/main.rs b/src/main.rs index 162e950cc..b79d79e67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -260,6 +260,7 @@ impl<'args> Exa<'args> { None, self.options.view.deref_links, self.options.view.total_size, + None, ) { Err(e) => { exit_status = 2;