diff --git a/Cargo.lock b/Cargo.lock index 013d08d..0252794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,6 +362,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "humantime" version = "2.1.0" @@ -595,6 +601,16 @@ dependencies = [ "adler2", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -752,6 +768,7 @@ dependencies = [ "ignore", "indoc", "log", + "num_cpus", "once_cell", "regex", "rustywind_core", diff --git a/rustywind-cli/Cargo.toml b/rustywind-cli/Cargo.toml index a239e44..36c56dd 100644 --- a/rustywind-cli/Cargo.toml +++ b/rustywind-cli/Cargo.toml @@ -47,3 +47,6 @@ ignore = "0.4" # parsing serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" + +# parallelism +num_cpus = "1.16" diff --git a/rustywind-cli/src/heard.rs b/rustywind-cli/src/heard.rs new file mode 100644 index 0000000..12492f7 --- /dev/null +++ b/rustywind-cli/src/heard.rs @@ -0,0 +1,41 @@ +/// Heard is a struct that handles running the rustywind on a list of files +/// in parallel. It uses the num_cpus crate to determine the number of +/// physical cores on the machine. It then spawns a thread for each core +/// and runs the rustywind on the file paths. +use crate::options::Options; +use std::{path::PathBuf, sync::Arc}; + +#[derive(Debug)] +pub struct Heard { + cpus: usize, + options: Arc, +} + +impl Heard { + pub fn new(options: Arc) -> Self { + let cpus = num_cpus::get_physical(); + Self { cpus, options } + } + + pub fn run_on_file_paths(self, file_paths: Vec) { + log::debug!("checking {} files", file_paths.len()); + + let total_chunks = self.cpus; + let chunks_of = file_paths.len() / total_chunks; + let options = &self.options; + + std::thread::scope(|s| { + file_paths.chunks(chunks_of).for_each(|chunk| { + s.spawn(|| { + run_on_file_paths(chunk, options); + }); + }); + }) + } +} + +fn run_on_file_paths(file_paths: &[PathBuf], options: &Options) { + for file_path in file_paths { + crate::run_on_file_path(file_path, options); + } +} diff --git a/rustywind-cli/src/main.rs b/rustywind-cli/src/main.rs index 94eaa68..3146c05 100644 --- a/rustywind-cli/src/main.rs +++ b/rustywind-cli/src/main.rs @@ -1,9 +1,11 @@ mod cli; +mod heard; mod options; use ahash::AHashSet as HashSet; use clap::Parser; use eyre::Result; +use heard::Heard; use indoc::indoc; use once_cell::sync::Lazy; use options::Options; @@ -14,6 +16,7 @@ use std::path::Path; use std::path::PathBuf; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; +use std::sync::Arc; static EXIT_ERROR: Lazy = Lazy::new(|| AtomicBool::new(false)); static GRAY: Lazy = Lazy::new(|| colored::CustomColor::new(120, 120, 120)); @@ -99,7 +102,12 @@ fn main() -> Result<()> { color_eyre::install()?; let cli = Cli::parse(); - let options = Options::new_from_cli(cli)?; + + let mut options = Options::new_from_cli(cli)?; + + let search_paths = std::mem::take(&mut options.search_paths); + + let options = Arc::new(options); let rustywind = &options.rustywind; match &options.write_mode { @@ -132,9 +140,8 @@ fn main() -> Result<()> { eprint!("[WARN] No classes were found in STDIN"); } } else { - for file_path in options.search_paths.iter() { - run_on_file_paths(file_path, &options) - } + let heard = Heard::new(options.clone()); + heard.run_on_file_paths(search_paths); // after running on all files, if there was an error, exit with 1 if EXIT_ERROR.load(Ordering::Relaxed) { @@ -145,7 +152,7 @@ fn main() -> Result<()> { Ok(()) } -fn run_on_file_paths(file_path: &Path, options: &Options) { +pub fn run_on_file_path(file_path: &Path, options: &Options) { // if the file is in the ignored_files list return early if should_ignore_current_file(&options.ignored_files, file_path) { log::debug!("file path {file_path:#?} found in ignored_files, will not sort"); @@ -175,7 +182,7 @@ fn run_on_file_paths(file_path: &Path, options: &Options) { (true, WriteMode::ToConsole) => print_file_contents(&sorted_content), (false, WriteMode::ToConsole) => print_file_contents(&sorted_content), - (_, WriteMode::CheckFormatted) => { + (contents_changed, WriteMode::CheckFormatted) => { print_changed_files(file_path, contents_changed, options); } } diff --git a/rustywind-core/src/app.rs b/rustywind-core/src/app.rs index 9bcfc45..bb47b80 100644 --- a/rustywind-core/src/app.rs +++ b/rustywind-core/src/app.rs @@ -10,7 +10,7 @@ use aho_corasick::{Anchored, Input}; use regex::Captures; /// The options to pass to the sorter. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct RustyWind { pub regex: FinderRegex, pub sorter: Sorter, diff --git a/rustywind-core/src/sorter.rs b/rustywind-core/src/sorter.rs index d7139e8..a47e33d 100644 --- a/rustywind-core/src/sorter.rs +++ b/rustywind-core/src/sorter.rs @@ -16,7 +16,7 @@ pub(crate) static SORTER_EXTRACTOR_RE: Lazy = Lazy::new(|| Regex::new(r"^(\.[^\s]+)[ ]").unwrap()); /// Use either our default regex in [crate::defaults::RE] or a custom regex. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum FinderRegex { DefaultRegex, CustomRegex(Regex), @@ -34,7 +34,7 @@ impl Deref for FinderRegex { } /// Use either our default sorter in [crate::defaults::SORTER] or a custom sorter. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Sorter { DefaultSorter, CustomSorter(HashMap),