another day, another boost: 30% faster on linux with getdents64

This commit is contained in:
rabite 2020-02-17 02:22:19 +01:00
parent f10f7d5e32
commit 963e0ae1cb
4 changed files with 200 additions and 0 deletions

24
Cargo.lock generated
View File

@ -214,6 +214,27 @@ name = "constant_time_eq"
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"
@ -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"

View File

@ -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 }

View File

@ -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();

View File

@ -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;