use std::{
env,
fs::{self, File},
os::fd::AsRawFd,
path::{Path, PathBuf},
time::SystemTime,
};
use brunch::{benches, Bench};
use libc::{c_void, SYS_getdents64};
use nix::{
fcntl::{open, OFlag},
sys::stat::Mode,
unistd::{lseek, unlinkat, Whence},
};
const BUFFER_SIZE: usize = 65536;
fn create_directory(count: usize) -> (PathBuf, Vec<String>) {
let mut dir = env::temp_dir();
let unique = format!(
"syd_getdents_bench_count_{}_{}",
count,
SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
dir.push(unique);
fs::create_dir_all(&dir).unwrap_or_else(|_| panic!("Failed to create directory: {:?}", &dir));
let dfd = open(&dir, OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::empty())
.expect("Failed to open directory FD");
let mut files = Vec::with_capacity(count);
for i in 0..count {
let filename = format!("file_{}", i);
let file_path = dir.join(&filename);
File::create(&file_path)
.unwrap_or_else(|_| panic!("Failed to create file: {:?}", &file_path));
files.push(filename);
}
let _ = nix::unistd::close(dfd);
(dir, files)
}
fn cleanup_directory(dir: &Path, files: &[String]) {
let dfd = open(dir, OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::empty());
if let Ok(dfd) = dfd {
for fname in files {
let _ = unlinkat(
&dfd,
Path::new(fname),
nix::unistd::UnlinkatFlags::NoRemoveDir,
);
}
}
let _ = fs::remove_dir_all(dir);
}
fn getdents_same_fd(dir: &Path) {
let fd = open(dir, OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::empty())
.expect("Failed to open directory (same-FD)");
let mut buffer = vec![0_u8; BUFFER_SIZE];
lseek(&fd, 0, Whence::SeekSet).expect("Failed to lseek to start");
loop {
let ret = unsafe {
libc::syscall(
SYS_getdents64,
fd.as_raw_fd(),
buffer.as_mut_ptr() as *mut c_void,
BUFFER_SIZE,
)
};
if ret < 0 {
panic!("getdents_same_fd: syscall error, returned {}", ret);
} else if ret == 0 {
break;
}
}
}
fn getdents_new_fd(dir: &Path) {
let fd = open(dir, OFlag::O_RDONLY | OFlag::O_DIRECTORY, Mode::empty())
.expect("Failed to open directory (new-FD)");
let mut buffer = vec![0_u8; BUFFER_SIZE];
loop {
let ret = unsafe {
libc::syscall(
SYS_getdents64,
fd.as_raw_fd(),
buffer.as_mut_ptr() as *mut c_void,
BUFFER_SIZE,
)
};
if ret < 0 {
panic!("getdents_new_fd: syscall error, returned {}", ret);
} else if ret == 0 {
break;
}
}
}
fn main() {
let counts = [1_usize, 16, 256, 4096];
let mut same_fd_setups = Vec::new();
for &count in &counts {
let (dir, files) = create_directory(count);
same_fd_setups.push((count, dir, files));
}
let mut new_fd_setups = Vec::new();
for &count in &counts {
let (dir, files) = create_directory(count);
new_fd_setups.push((count, dir, files));
}
benches!(
inline:
Bench::new("GetdentsSameFD(count=1)").run(|| {
getdents_same_fd(&same_fd_setups[0].1);
}),
Bench::new("GetdentsSameFD(count=16)").run(|| {
getdents_same_fd(&same_fd_setups[1].1);
}),
Bench::new("GetdentsSameFD(count=256)").run(|| {
getdents_same_fd(&same_fd_setups[2].1);
}),
Bench::new("GetdentsSameFD(count=4096)").run(|| {
getdents_same_fd(&same_fd_setups[3].1);
}),
Bench::new("GetdentsNewFD(count=1)").run(|| {
getdents_new_fd(&new_fd_setups[0].1);
}),
Bench::new("GetdentsNewFD(count=16)").run(|| {
getdents_new_fd(&new_fd_setups[1].1);
}),
Bench::new("GetdentsNewFD(count=256)").run(|| {
getdents_new_fd(&new_fd_setups[2].1);
}),
Bench::new("GetdentsNewFD(count=4096)").run(|| {
getdents_new_fd(&new_fd_setups[3].1);
}),
);
for (_, dir, files) in same_fd_setups {
cleanup_directory(&dir, &files);
}
for (_, dir, files) in new_fd_setups {
cleanup_directory(&dir, &files);
}
}