diff options
author | rabite <rabite@posteo.de> | 2020-02-17 02:22:19 +0100 |
---|---|---|
committer | rabite <rabite@posteo.de> | 2020-02-18 21:47:41 +0100 |
commit | 963e0ae1cb7ff788cff3003ccc66d466156404d0 (patch) | |
tree | 00d32519d8f381842485b8bec0c734ef3cadcc3c | |
parent | f10f7d5e32836a6f95ec103f026fd7d22b9b5a54 (diff) |
another day, another boost: 30% faster on linux with getdents64
-rw-r--r-- | Cargo.lock | 24 | ||||
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/files.rs | 174 | ||||
-rw-r--r-- | src/main.rs | 1 |
4 files changed, 200 insertions, 0 deletions
@@ -215,6 +215,27 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "crossbeam-deque" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -594,6 +615,7 @@ dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "dirs-2 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1716,6 +1738,8 @@ dependencies = [ "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum color_quant 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" "checksum constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +"checksum crossbeam 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" "checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" "checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" "checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" @@ -45,6 +45,7 @@ derivative = "1.0.3" itertools = "0.8" nix = "0.17" strip-ansi-escapes = "0.1" +crossbeam = "0.7" image = { version = "0.21.1", optional = true } diff --git a/src/files.rs b/src/files.rs index e2b2022..78b5790 100644 --- a/src/files.rs +++ b/src/files.rs @@ -104,6 +104,8 @@ pub enum FileError { OpenDir(#[cause] nix::Error), #[fail(display = "Couldn't read files! Error: {}", _0)] ReadFiles(#[cause] nix::Error), + #[fail(display = "Had problems with getdents64 in directory: {}", _0)] + GetDents(String), } pub fn get_pool() -> ThreadPool { @@ -383,7 +385,179 @@ impl Drop for Files { } +#[cfg(target_os = "linux")] +#[repr(C)] +#[derive(Clone, Debug)] +pub struct linux_dirent { + pub d_ino: u64, + pub d_off: u64, + pub d_reclen: u16, + pub d_type: u8, + pub d_name: [u8; 0], +} + + +// This arcane spell hastens the target by around 30%. + +// It uses quite a bit of usafe code, mostly to call into libc and +// dereference raw pointers inherent to the getdents API, but also to +// avoid some of the overhead built into Rust's default conversion +// methods. How the getdents64 syscall is intended to be used was +// mostly looked up in man 2 getdents64, the nc crate, and the +// upcoming version of the walkdir crate, plus random examples here +// and there.. + +// This should probably be replaced with walkdir when it gets a proper +// release with the new additions. nc itself is already too high level +// to meet the performance target, unfortunately. +#[cfg(target_os = "linux")] +pub fn from_getdents(fd: i32, path: &Path, nothidden: &AtomicUsize) -> Vec<File> +{ + use libc::SYS_getdents64; + + // Nice big 4MB buffer + const BUFFER_SIZE: usize = 1024 * 1024 * 4; + + let mut buf: Vec<u8> = vec![0; BUFFER_SIZE]; + let bufptr = buf.as_mut_ptr(); + let files = std::sync::Mutex::new(vec![]); + let files = &files; + + crossbeam::scope(|s| { + loop { + let nread = unsafe { libc::syscall(SYS_getdents64, fd, bufptr, BUFFER_SIZE) }; + + if nread <= 0 { + break; + } + + // Clone buffer for processing in another thread and fetch more entries + let mut buf: Vec<u8> = buf.clone(); + + s.spawn(move |_| { + let cap = nread as usize / std::mem::size_of::<linux_dirent>(); + let mut localfiles = Vec::with_capacity(cap); + let bufptr = buf.as_mut_ptr(); + let mut bpos: usize = 0; + + while bpos < nread as usize { + let d: &linux_dirent = unsafe { std::mem::transmute(bufptr as usize + bpos as usize) }; + + // Name lenegth is overallocated, true length can be found by checking with strlen + let name_len = d.d_reclen as usize - + std::mem::size_of::<u64>() - + std::mem::size_of::<u64>() - + std::mem::size_of::<u16>() - + std::mem::size_of::<u8>(); + + // OOB!!! + if bpos + name_len > BUFFER_SIZE { + HError::log::<()>(&format!("WARNING: Name for file was out of bounds in: {}", + path.to_string_lossy())).ok(); + return; + } + + // Doing it here simplifies skipping + bpos = bpos + d.d_reclen as usize; + + let name: &OsStr = { + let true_len = unsafe { libc::strlen(d.d_name.as_ptr() as *const i8) }; + let bytes: &[u8] = unsafe { std::slice::from_raw_parts(d.d_name.as_ptr() as *const u8, + true_len) }; + + // Don't want this + if bytes.len() == 0 || bytes == b"." || bytes == b".." { + continue; + } + + unsafe { std::mem::transmute(bytes) } + }; + + + // Avoid reallocation on push + let mut pathstr = std::ffi::OsString::with_capacity(path.as_os_str().len() + + name.len() + + 2); + pathstr.push(path.as_os_str()); + pathstr.push("/"); + pathstr.push(name); + + let path = PathBuf::from(pathstr); + + let name = name.to_str() + .map(|n| String::from(n)) + .unwrap_or_else(|| name.to_string_lossy().to_string()); + + + let hidden = name.as_bytes()[0] == b'.'; + + if !hidden { + nothidden.fetch_add(1, Ordering::Relaxed); + } + + let kind = match d.d_type { + 4 => Kind::Directory, + _ => Kind::File, + }; + + let file = File { + name: name, + hidden: hidden, + kind: kind, + path: path, + dirsize: None, + target: None, + meta: None, + selected: false, + tag: None, + }; + + localfiles.push(file); + + + } + + files.lock().unwrap().append(&mut localfiles); + }); + } + }).unwrap(); + + return std::mem::take(&mut *files.lock().unwrap()); +} + + impl Files { + // Use getdents64 on Linux + #[cfg(target_os = "linux")] + pub fn new_from_path_cancellable(path: &Path, stale: Stale) -> HResult<Files> { + use std::os::unix::io::AsRawFd; + + let nonhidden = AtomicUsize::default(); + + let dir = Dir::open(path.clone(), + OFlag::O_DIRECTORY, + Mode::empty()) + .map_err(|e| FileError::OpenDir(e))?; + + let direntries = from_getdents(dir.as_raw_fd(), path, &nonhidden); + + if stale.is_stale()? { + HError::stale()?; + } + + let mut files = Files::default(); + files.directory = File::new_from_path(&path)?; + + + files.files = direntries; + files.len = nonhidden.load(Ordering::Relaxed); + files.stale = Some(stale); + + Ok(files) + } + + + #[cfg(not(target_os = "linux"))] pub fn new_from_path_cancellable(path: &Path, stale: Stale) -> HResult<Files> { let nonhidden = AtomicUsize::default(); diff --git a/src/main.rs b/src/main.rs index 72f1d3b..1ce0d87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,7 @@ extern crate strum_macros; extern crate derivative; extern crate nix; extern crate strip_ansi_escapes; +extern crate crossbeam; extern crate osstrtools; extern crate pathbuftools; |