commit fca667e3801cd8d0839dc5fe7ca70167b375afc8 Author: eiiko6 Date: Mon Mar 23 22:54:03 2026 +0100 base lib with cpu benchmark and basic cli diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..0c7d18e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,400 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "clap" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "slimefetch" +version = "0.1.0" +dependencies = [ + "clap", + "colored", + "num_cpus", + "sysinfo", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5ab85f4 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "slimefetch" +version = "0.1.0" +edition = "2024" + +[dependencies] +clap = { version = "4.6.0", features = ["derive"] } +colored = "3.1.1" +num_cpus = "1.17.0" +sysinfo = "0.38.4" diff --git a/src/benchmark.rs b/src/benchmark.rs new file mode 100644 index 0000000..48a7a8e --- /dev/null +++ b/src/benchmark.rs @@ -0,0 +1,166 @@ +use std::{ + sync::{ + Arc, Mutex, + atomic::{AtomicUsize, Ordering}, + }, + thread, + time::{Duration, Instant}, +}; + +// Factor determining how many times the calculation runs in the multi-threaded test. +pub const MULTI_THREAD_LOAD_FACTOR: usize = 32; + +pub struct BenchmarkResults { + pub duration: Duration, + pub primes_found: u64, + pub score: u64, + pub batch_count: u64, +} + +pub fn run_benchmark_singlethread(prime_limit: u64) -> BenchmarkResults { + let start_time = Instant::now(); + let primes_found = calculate_primes(1, prime_limit); + let duration = start_time.elapsed(); + + let score = calculate_score(duration, 1, prime_limit); + + BenchmarkResults { + duration, + primes_found, + score, + batch_count: 1, + } +} + +pub fn run_benchmark_multithread(prime_limit: u64, jobs: usize) -> BenchmarkResults { + let start_time = Instant::now(); + + // The total number of calculation batches to perform across all threads + let total_jobs_to_process = MULTI_THREAD_LOAD_FACTOR; + + // Atomic counter for distributing jobs to threads without heavy locking + let shared_job_index_counter = Arc::new(AtomicUsize::new(0)); + // Mutex protected accumulator for the total primes found across all threads + let shared_total_prime_count = Arc::new(Mutex::new(0)); + // Vector to store the execution duration of each individual thread + let shared_thread_durations = Arc::new(Mutex::new(Vec::new())); + + let mut thread_handles = vec![]; + + for _ in 0..jobs { + let job_counter_reference = Arc::clone(&shared_job_index_counter); + let prime_count_reference = Arc::clone(&shared_total_prime_count); + let durations_reference = Arc::clone(&shared_thread_durations); + let limit_per_run = prime_limit; + + let handle = thread::spawn(move || { + let thread_execution_start = Instant::now(); + let mut local_thread_prime_count = 0; + + loop { + // Fetch the next job index and increment atomically + let job_index = job_counter_reference.fetch_add(1, Ordering::Relaxed); + + if job_index >= total_jobs_to_process { + break; + } + + local_thread_prime_count += calculate_primes(1, limit_per_run); + } + + let thread_execution_duration = thread_execution_start.elapsed(); + + // Record thread duration + durations_reference + .lock() + .unwrap() + .push(thread_execution_duration); + + // Commit local count to the global sum + let mut global_count_lock = prime_count_reference.lock().unwrap(); + *global_count_lock += local_thread_prime_count; + }); + thread_handles.push(handle); + } + + // Wait for all threads to complete + for handle in thread_handles { + handle.join().unwrap(); + } + + let duration = start_time.elapsed(); + let final_count = *shared_total_prime_count.lock().unwrap(); + + // Calculate average time a thread spent working + let durations_guard = shared_thread_durations.lock().unwrap(); + let total_thread_microseconds: u128 = durations_guard.iter().map(|d| d.as_micros()).sum(); + + let average_thread_microseconds = if !durations_guard.is_empty() { + total_thread_microseconds / durations_guard.len() as u128 + } else { + 0 + }; + let average_thread_duration = Duration::from_micros(average_thread_microseconds as u64); + + // Score normalized by load factor to represent "Speed per unit of work" + let score = calculate_score(duration, MULTI_THREAD_LOAD_FACTOR as u64, prime_limit); + + BenchmarkResults { + duration, + score, + primes_found: final_count, + batch_count: MULTI_THREAD_LOAD_FACTOR as u64, + } +} + +/// Performs the CPU-intensive prime number calculation. +pub fn calculate_primes(range_start: u64, range_end: u64) -> u64 { + let mut prime_count = 0; + let mut current_number = range_start; + + if current_number <= 2 { + if range_end >= 2 { + prime_count += 1; + } + current_number = 3; + } + + if current_number % 2 == 0 { + current_number += 1; + } + + while current_number <= range_end { + if is_number_prime(current_number) { + prime_count += 1; + } + current_number += 2; + } + prime_count +} + +/// Helper function to check primality. +pub fn is_number_prime(number: u64) -> bool { + if number <= 1 { + return false; + } + + let search_limit = (number as f64).sqrt() as u64; + + for i in (3..=search_limit).step_by(2) { + if number % i == 0 { + return false; + } + } + true +} + +/// Calculates a score based on work done divided by time taken. +pub fn calculate_score(duration: Duration, batch_multiplier: u64, prime_limit: u64) -> u64 { + let microseconds = duration.as_micros() as u64; + + if microseconds == 0 { + return 0; + } + + (prime_limit * batch_multiplier * 10_000) / microseconds +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..2dafb92 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,18 @@ +// use colored::Colorize; + +mod benchmark; +mod slimes; + +pub fn application_header() -> &'static str { + let ascii_art = r#" + .---. + .' '. < CPU SLIME > + / ^ ^ \ + : v : (Benchmarking) + | | + \ / + '._____.' + "#; + // println!("{}", ascii_art.bright_green().bold()); + ascii_art +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..370da8e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,84 @@ +use clap::Parser; +use colored::Colorize; +use slimefetch::application_header; + +mod benchmark; +use crate::benchmark::{BenchmarkResults, run_benchmark_multithread, run_benchmark_singlethread}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +pub struct Cli { + /// Skip CPU benchmark + #[arg(short, long)] + pub skip_benchmark: bool, + + /// Benchmark: Upper limit for prime calculation (higher number = longer test) + #[arg(short, long, default_value_t = 500_000)] + pub prime_limit: u64, + + /// Benchmark: Enforce cpu thread amount to use. + /// Defaults to detected cpu core count. + #[arg(short, long)] + pub jobs: Option, + + /// Enable verbose output logging + #[arg(short, long)] + pub verbose: bool, +} + +fn main() { + let cli = Cli::parse(); + + println!("{}", application_header().bright_blue()); + + let logical_core_count = match cli.jobs { + Some(j) => j, + None => num_cpus::get(), + }; + + print_section_header("Single Threaded CPU Benchmark"); + let singlethread_benchmark = run_benchmark_singlethread(cli.prime_limit); + print_detailed_result(&singlethread_benchmark); + print_section_header("Multi Threaded CPU Benchmark"); + let multithread_benchmark = run_benchmark_multithread(cli.prime_limit, logical_core_count); + print_detailed_result(&multithread_benchmark); + + let multi_thread_speedup_ratio = if singlethread_benchmark.score > 0 { + multithread_benchmark.score / singlethread_benchmark.score + } else { + 0 + }; + + let scaling_color_formatter = + if multi_thread_speedup_ratio as f64 > (logical_core_count as f64 * 0.7) { + |s: String| s.green() + } else { + |s: String| s.yellow() + }; + + println!( + "Parallel Scaling : {}", + scaling_color_formatter(format!("{:.2}x", multi_thread_speedup_ratio)).bold() + ); +} + +pub fn print_section_header(title_text: &str) { + println!( + "{}", + format!("[ {} ]", title_text).white().bold().underline() + ); +} + +pub fn print_detailed_result(results: &BenchmarkResults) { + println!( + " Batch Count : {} batch{}", + results.batch_count, + if results.batch_count > 1 { "es" } else { "" } + ); + println!(" Total Duration : {:.4}s", results.duration.as_secs_f64()); + println!(" Primes Found : {}", results.primes_found); + println!( + " Calculated Score: {}", + format!("{}", results.score).green().bold() + ); +} diff --git a/src/slimes.rs b/src/slimes.rs new file mode 100644 index 0000000..51a172d --- /dev/null +++ b/src/slimes.rs @@ -0,0 +1,18 @@ +use colored::Color; +use colored::Colorize; + +trait Slime { + fn label(&self) -> String; + fn value(&self) -> String; + fn icon(&self) -> String; + fn color(&self) -> Color; + + fn print(&self) { + println!( + "{} {:<12} {}", + self.icon().color(self.color()), + format!("{}:", self.label()).bold().color(self.color()), + self.value().white() + ); + } +}