use std::{
hint::black_box,
sync::{Arc, Barrier},
thread,
time::Duration,
};
use brunch::{benches, Bench};
use libc::{_exit, c_int, close, fork, pipe, read, waitpid, write, WEXITSTATUS, WIFEXITED};
use nix::errno::Errno;
fn busy(max: i32) -> i32 {
let mut count = 0;
for i in 1..max {
for j in 2..(i / 2) {
if i % j == 0 {
count += 1;
}
}
}
black_box(count)
}
fn bm_cpubound_uniprocess() {
busy(250);
}
fn bm_cpubound_asymmetric(iterations: usize) {
unsafe {
let child = fork();
if child == 0 {
for _ in 0..iterations {
busy(250);
}
_exit(0);
} else if child < 0 {
panic!("fork() failed");
} else {
let mut status: c_int = 0;
let w = waitpid(child, &mut status as *mut c_int, 0);
if w < 0 {
panic!("waitpid() failed: {:?}", Errno::last());
}
if WIFEXITED(status) && WEXITSTATUS(status) == 0 {
} else {
panic!("Child did not exit(0).");
}
}
}
}
fn bm_cpubound_symmetric(procs: usize, max_iters: usize) {
let mut children = Vec::new();
let mut total_done = 0;
for _ in 0..procs {
let remaining = max_iters - total_done;
if remaining == 0 {
break;
}
let cur = remaining / (procs - children.len());
let cur = if cur == 0 { remaining } else { cur };
total_done += cur;
unsafe {
let child = fork();
if child == 0 {
for _ in 0..cur {
busy(250);
}
_exit(0);
} else if child < 0 {
panic!("fork() failed in symmetric");
} else {
if cur > 0 {
}
children.push(child);
}
}
}
unsafe {
for &ch in &children {
let mut status: c_int = 0;
let w = waitpid(ch, &mut status, 0);
if w < 0 {
panic!("waitpid() failed");
}
if WIFEXITED(status) && WEXITSTATUS(status) == 0 {
} else {
panic!("Child did not exit(0).");
}
}
}
}
fn switch_child_loop(read_fd: c_int, write_fd: c_int) {
let mut buf = [0u8; 1];
loop {
let n = unsafe { read(read_fd, buf.as_mut_ptr() as *mut _, 1) };
if n == 0 {
break;
} else if n < 0 {
let e = Errno::last();
panic!("Child read() error: {:?}", e);
}
let w = unsafe { write(write_fd, buf.as_ptr() as *const _, 1) };
if w < 0 {
let e = Errno::last();
if e == Errno::EPIPE {
break;
}
panic!("Child write() error: {:?}", e);
}
if w == 0 {
break;
}
}
}
fn bm_process_switch(num_processes: usize, iterations: usize) {
if num_processes < 2 {
return; }
let mut read_fds = Vec::with_capacity(num_processes);
let mut write_fds = Vec::with_capacity(num_processes);
unsafe {
for _ in 0..num_processes {
let mut fds = [0; 2];
if pipe(fds.as_mut_ptr()) < 0 {
panic!("pipe() failed");
}
read_fds.push(fds[0]);
write_fds.push(fds[1]);
}
let mut children = Vec::new();
for i in 1..num_processes {
let read_index = i;
let write_index = (i + 1) % num_processes;
let child = fork();
if child == 0 {
for j in 0..num_processes {
if j != read_index {
close(read_fds[j]);
}
if j != write_index {
close(write_fds[j]);
}
}
switch_child_loop(read_fds[read_index], write_fds[write_index]);
_exit(0);
} else if child < 0 {
panic!("fork() failed in BM_ProcessSwitch");
} else {
children.push(child);
}
}
let read_idx = 0;
let write_idx = 1;
let mut c = [b'a'];
if write(write_fds[write_idx], c.as_ptr() as *const _, 1) != 1 {
panic!("initial write failed");
}
for _ in 0..iterations {
if read(read_fds[read_idx], c.as_mut_ptr() as *mut _, 1) != 1 {
panic!("read in parent failed");
}
if write(write_fds[write_idx], c.as_ptr() as *const _, 1) != 1 {
panic!("write in parent failed");
}
}
for i in 0..num_processes {
close(read_fds[i]);
close(write_fds[i]);
}
for &ch in &children {
let mut status: c_int = 0;
if waitpid(ch, &mut status, 0) < 0 {
panic!("waitpid failed in BM_ProcessSwitch");
}
if !WIFEXITED(status) || WEXITSTATUS(status) != 0 {
panic!("child exit code not 0");
}
}
}
}
fn bm_thread_switch(num_threads: usize, iterations: usize) {
if num_threads < 2 {
return;
}
let mut read_fds = Vec::new();
let mut write_fds = Vec::new();
unsafe {
for _ in 0..num_threads {
let mut fds = [0; 2];
if pipe(fds.as_mut_ptr()) < 0 {
panic!("pipe() failed for thread_switch");
}
read_fds.push(fds[0]);
write_fds.push(fds[1]);
}
}
let mut handles = Vec::with_capacity(num_threads - 1);
for i in 1..num_threads {
let read_idx = i;
let write_idx = (i + 1) % num_threads;
let rfd = read_fds[read_idx];
let wfd = write_fds[write_idx];
let handle = thread::spawn(move || {
switch_child_loop(rfd, wfd);
unsafe {
close(rfd);
close(wfd);
}
});
handles.push(handle);
}
let read_idx = 0;
let write_idx = 1;
let c = [b'a'];
unsafe {
if write(write_fds[write_idx], c.as_ptr() as *const _, 1) != 1 {
panic!("thread main initial write failed");
}
}
let mut c = [0u8; 1];
for _ in 0..iterations {
unsafe {
if read(read_fds[read_idx], c.as_mut_ptr() as *mut _, 1) != 1 {
panic!("thread main read failed");
}
if write(write_fds[write_idx], c.as_ptr() as *const _, 1) != 1 {
panic!("thread main write failed");
}
}
}
unsafe {
close(read_fds[read_idx]);
close(write_fds[write_idx]);
}
for h in handles {
let _ = h.join();
}
}
fn bm_thread_start(num_threads: usize, iterations: usize) {
for _ in 0..iterations {
let barrier = Arc::new(Barrier::new(num_threads + 1));
let mut threads = Vec::with_capacity(num_threads);
for _ in 0..num_threads {
let b = barrier.clone();
threads.push(thread::spawn(move || {
b.wait();
}));
}
barrier.wait();
for t in threads {
let _ = t.join();
}
}
}
fn bm_process_lifecycle(num_procs: usize, iterations: usize) {
unsafe {
let mut pids = Vec::with_capacity(num_procs);
for _ in 0..iterations {
pids.clear();
for _i in 0..num_procs {
let pid = fork();
if pid == 0 {
_exit(0);
} else if pid < 0 {
panic!("fork() failed in process_lifecycle");
} else {
pids.push(pid);
}
}
for &p in &pids {
let mut status = 0;
let w = waitpid(p, &mut status, 0);
if w < 0 {
panic!("waitpid() failed in process_lifecycle");
}
if !WIFEXITED(status) || WEXITSTATUS(status) != 0 {
panic!("child exit code not 0 in process_lifecycle");
}
}
}
}
}
fn main() {
benches!(
inline:
Bench::new("BM_CPUBoundUniprocess").run(|| {
bm_cpubound_uniprocess();
}),
Bench::new("BM_CPUBoundAsymmetric").run(|| {
bm_cpubound_asymmetric(100);
}),
Bench::new("BM_CPUBoundSymmetric(2 procs)").run(|| {
bm_cpubound_symmetric(2, 100);
}),
Bench::new("BM_CPUBoundSymmetric(4 procs)").run(|| {
bm_cpubound_symmetric(4, 100);
}),
Bench::new("BM_CPUBoundSymmetric(8 procs)").run(|| {
bm_cpubound_symmetric(8, 100);
}),
Bench::new("BM_CPUBoundSymmetric(16 procs)").run(|| {
bm_cpubound_symmetric(16, 100);
}),
Bench::new("BM_ProcessSwitch(2 procs)").run(|| {
bm_process_switch(2, 1000);
}),
Bench::new("BM_ProcessSwitch(4 procs)").run(|| {
bm_process_switch(4, 1000);
}),
Bench::new("BM_ProcessSwitch(8 procs)").run(|| {
bm_process_switch(8, 1000);
}),
Bench::new("BM_ProcessSwitch(16 procs)").run(|| {
bm_process_switch(16, 1000);
}),
Bench::new("BM_ThreadSwitch(2 threads)").run(|| {
bm_thread_switch(2, 1000);
}),
Bench::new("BM_ThreadSwitch(4 threads)").run(|| {
bm_thread_switch(4, 1000);
}),
Bench::new("BM_ThreadSwitch(8 threads)").run(|| {
bm_thread_switch(8, 1000);
}),
Bench::new("BM_ThreadSwitch(16 threads)").run(|| {
bm_thread_switch(16, 1000);
}),
Bench::new("BM_ThreadStart(1)").run(|| {
bm_thread_start(1, 10);
}),
Bench::new("BM_ThreadStart(64)")
.with_timeout(Duration::from_secs(30))
.run(|| {
bm_thread_start(64, 10);
}),
Bench::new("BM_ThreadStart(128)")
.with_timeout(Duration::from_secs(30))
.run(|| {
bm_thread_start(128, 10);
}),
Bench::new("BM_ThreadStart(1024)")
.with_timeout(Duration::from_secs(30))
.run(|| {
bm_thread_start(1024, 10);
}),
Bench::new("BM_ProcessLifecycle(1 proc)").run(|| {
bm_process_lifecycle(1, 10);
}),
Bench::new("BM_ProcessLifecycle(64 procs)")
.with_timeout(Duration::from_secs(30))
.run(|| {
bm_process_lifecycle(64, 10);
}),
Bench::new("BM_ProcessLifecycle(128 procs)")
.with_timeout(Duration::from_secs(60))
.run(|| {
bm_process_lifecycle(128, 10);
}),
Bench::new("BM_ProcessLifecycle(512 procs)")
.with_timeout(Duration::from_secs(150))
.run(|| {
bm_process_lifecycle(512, 10);
}),
);
}