From a6d56af613bac896836472f1a84f3616aeffd5f5 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Thu, 5 May 2022 13:19:24 +0200 Subject: [PATCH] add cli option to specify number of threads --- Cargo.lock | 144 ++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 11 +- src/population.rs | 369 +++++++++++++++++++++++++++++++--------------- 4 files changed, 405 insertions(+), 120 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1046a53..7d6f84c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,18 +2,74 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "lazy_static", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "console" version = "0.15.0" @@ -52,6 +108,21 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "indexmap" version = "1.6.2" @@ -62,6 +133,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.124" @@ -74,6 +151,12 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +[[package]] +name = "os_str_bytes" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" + [[package]] name = "parameterized" version = "1.0.0" @@ -101,6 +184,30 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.37" @@ -168,11 +275,18 @@ checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" name = "rusty_propagation" version = "0.1.0" dependencies = [ + "clap", "console", "parameterized", "rand", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" version = "1.0.92" @@ -184,6 +298,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.1.17" @@ -194,6 +317,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + [[package]] name = "unicode-width" version = "0.1.9" @@ -206,6 +335,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" @@ -228,6 +363,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index a183b2a..77a7910 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" console = "0.15.0" rand = "0.8.5" parameterized = "1.0.0" +clap = { version = "3.0", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index b0186ea..7959e68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use clap::Parser; + mod disease; mod human; mod population; @@ -13,7 +15,14 @@ mod prelude { use prelude::*; +#[derive(Parser)] +struct Cli { + /// Number of threads to use + threads: usize, +} + fn main() { + let args = Cli::parse(); let term = Term::stdout(); term.write_line("********** Rusty Propagation (Console) 2022 **********") .expect("Oops Looks like we have a problem here..."); @@ -29,7 +38,7 @@ fn main() { let mut counter: u32 = 0; loop { counter += 1; - stats = population.propagate(); + stats = population.propagate(args.threads); //population.display(); println!( "Normal: {} Infecteds: {} Immunes: {} Deads: {}", diff --git a/src/population.rs b/src/population.rs index 21622b4..6fb12cf 100644 --- a/src/population.rs +++ b/src/population.rs @@ -1,7 +1,8 @@ + use std::sync::{Arc, RwLock}; use crate::prelude::*; use std::thread; - +use std::time::{Instant}; #[derive(Debug)] pub struct Point { @@ -84,30 +85,31 @@ impl Population { // self.plague = plague; // } - fn is_inside(&self, pos: &Point) -> bool { - if pos.x >= 0 && pos.x < self.width && pos.y >= 0 && pos.y < self.height { - true - } else { - false - } - } + // fn is_inside(&self, pos: &Point) -> bool { + // if pos.x >= 0 && pos.x < self.width && pos.y >= 0 && pos.y < self.height { + // true + // } else { + // false + // } + // } - fn is_inside_and_infected(&self, point: Point) -> bool { - let humans = Arc::clone(&self.humans); - if self.is_inside(&point) { - let idx = human_idx(point.x, point.y, self.width); - let humans = humans.read().unwrap(); - if humans[idx].present_state == State::Infected { - roll(self.plague.infection_rate) - } else { - false - } - } else { - false - } - } + // fn is_inside_and_infected(&self, point: Point) -> bool { + // let humans = Arc::clone(&self.humans); + // if self.is_inside(&point) { + // let idx = human_idx(point.x, point.y, self.width); + // let humans = humans.read().unwrap(); + // if humans[idx].present_state == State::Infected { + // roll(self.plague.infection_rate) + // } else { + // false + // } + // } else { + // false + // } + // } - pub fn propagate(&mut self) -> [i32; 4] { + pub fn propagate(&mut self, num_threads: usize) -> [i32; 4] { + let timer = Instant::now(); let mut people_to_check: Vec = Vec::with_capacity(self.size); let mut possible_infected: Vec = @@ -121,101 +123,209 @@ impl Population { let mut stats: [i32; 4] = [0, 0, 0, 0]; // stats[0] Normal stats[1] Infected stats[2] Immune stats[3] Dead - { + let mut threads = vec![]; + + let len: usize = (self.width * self.height) as usize; + let mut lines = (len / num_threads) as usize; + if len % num_threads != 0 { + lines += 1; + } + for x in 0..num_threads { + let lower_bound = x * lines; + let mut upper_bound = (x + 1) * lines; + if upper_bound > len { + upper_bound = len; + }; + let humans = Arc::clone(&self.humans); - let humans = humans.read().unwrap(); - for h in humans.iter() { - match h.present_state { - State::Normal => { - possible_infected.push(Point{ x: h.x, y: h.y}); - stats[0] += 1; - } - State::Infected => { - people_to_check.push(Point { x: h.x, y: h.y }); - stats[1] += 1; - } - State::Immune => { - stats[2] += 1; - } - State::Dead => { - stats[3] += 1; + threads.push(thread::spawn(move || { + let mut possible_infected: Vec =Vec::with_capacity(lines); + let mut people_to_check: Vec =Vec::with_capacity(lines); + let mut stats: [i32; 4] = [0, 0, 0, 0]; + let humans = humans.read().unwrap(); + + for idx in lower_bound..upper_bound { + let h = &humans[idx]; + match h.present_state { + State::Normal => { + possible_infected.push(Point{ x: h.x, y: h.y}); + stats[0] += 1; + } + State::Infected => { + people_to_check.push(Point { x: h.x, y: h.y }); + stats[1] += 1; + } + State::Immune => { + stats[2] += 1; + } + State::Dead => { + stats[3] += 1; + } } } + (possible_infected, people_to_check, stats) + })); + } + + for t in threads { + let mut res = t.join().unwrap(); + possible_infected.append(&mut res.0); + people_to_check.append(&mut res.1); + for x in 0..4 { + stats[x] += res.2[x]; } } - // for pos in &people_to_check { - for pos in people_to_check.iter() { - //people_to_check.iter().map(|pos|{ - //get all the other people next to me and check if i die cure or infect - //now we can start to check if people would be infected or not - //let idx = human_idx(pos.x as i32, pos.y as i32, self.width as i32); - if roll(self.plague.curing_rate) { - //checks if the man recovers - people_to_cure.push(Point { x: pos.x, y: pos.y }); - } else { - if roll(self.plague.death_rate) { - //cheks if the man dies - people_to_kill.push(Point { x: pos.x, y: pos.y }); - } - } - } - for pos in possible_infected.iter() { - // infect human if someone near is infected - let infected: bool = self.is_inside_and_infected( - Point { - x: pos.x - 1, - y: pos.y - 1, - }, - ) || //Top Left - self.is_inside_and_infected( - Point { - x: pos.x, - y: pos.y - 1, - }, - ) || //Top - self.is_inside_and_infected( - Point { - x: pos.x + 1, - y: pos.y - 1, - }, - ) || //Top Right - self.is_inside_and_infected( - Point { - x: pos.x - 1, - y: pos.y, - }, - ) || //Left - self.is_inside_and_infected( - Point { - x: pos.x + 1, - y: pos.y, - }, - ) || //Right - self.is_inside_and_infected( - Point { - x: pos.x - 1, - y: pos.y + 1, - }, - ) || //Bottom Left - self.is_inside_and_infected( - Point { - x: pos.x, - y: pos.y + 1, - }, - ) || //Bottom - self.is_inside_and_infected( - Point { - x: pos.x + 1, - y: pos.y + 1, - }, - ); //Bottom Right - if infected { - people_to_infect.push(Point { x: pos.x, y: pos.y }); - } - } + #[cfg(debug_assertions)] + println!("after first pass {:?}", timer.elapsed()); + + debug_assert_eq!( + stats[0] + stats[1] + stats[2] + stats[3], + self.size as i32 + ); + let mut threads = vec![]; + + let len: usize = people_to_check.len(); + let mut lines = (len / num_threads) as usize; + if len % num_threads != 0 { + lines += 1; + } + let people_to_check_arc = Arc::new(people_to_check); + for x in 0..num_threads { + let lower_bound = x * lines; + let mut upper_bound = (x + 1) * lines; + if upper_bound > len { + upper_bound = len; + }; + let curing_rate = self.plague.curing_rate; + let death_rate = self.plague.death_rate; + let people_to_check = Arc::clone(&people_to_check_arc); + threads.push(thread::spawn(move || { + let mut people_to_cure: Vec =Vec::with_capacity(lines); + let mut people_to_kill: Vec =Vec::with_capacity(lines); + for idx in lower_bound..upper_bound { + let pos = &people_to_check[idx]; + if roll(curing_rate) { + //checks if the man recovers + people_to_cure.push(Point { x: pos.x, y: pos.y }); + } else { + if roll(death_rate) { + //cheks if the man dies + people_to_kill.push(Point { x: pos.x, y: pos.y }); + } + } + } + (people_to_cure, people_to_kill) + })); + } + for t in threads { + let mut res = t.join().unwrap(); + people_to_cure.append(&mut res.0); + people_to_kill.append(&mut res.1); + } + + #[cfg(debug_assertions)] + println!("after people_to_check {:?}", timer.elapsed()); + + // check normal people to see if someone near is infected + + let mut threads = vec![]; + + let len: usize = possible_infected.len(); + let mut lines = (len / num_threads) as usize; + if len % num_threads != 0 { + lines += 1; + } + let possible_infected_arc = Arc::new(possible_infected); + + { + let (width, height) = (self.width, self.height); + let infection_rate = self.plague.infection_rate; + + for x in 0..num_threads { + let humans = Arc::clone(&self.humans); + + + let lower_bound = x * lines; + let mut upper_bound = (x + 1) * lines; + if upper_bound > len { + upper_bound = len; + }; + let possible_infected = Arc::clone(&possible_infected_arc); + threads.push(thread::spawn(move || { + let humans = &humans.read().unwrap().to_vec(); + let mut people_to_infect: Vec =Vec::with_capacity(lines); + for idx in lower_bound..upper_bound { + let pos = &possible_infected[idx]; + + let infected: bool = fn_is_inside_and_infected( + Point { + x: pos.x - 1, + y: pos.y - 1, + }, width, height, humans, infection_rate + ) || //Top Left + fn_is_inside_and_infected( + Point { + x: pos.x, + y: pos.y - 1, + }, width, height, humans, infection_rate + ) || //Top + fn_is_inside_and_infected( + Point { + x: pos.x + 1, + y: pos.y - 1, + }, width, height, humans, infection_rate + ) || //Top Right + fn_is_inside_and_infected( + Point { + x: pos.x - 1, + y: pos.y, + }, width, height, humans, infection_rate + ) || //Left + fn_is_inside_and_infected( + Point { + x: pos.x + 1, + y: pos.y, + }, width, height, humans, infection_rate + ) || //Right + fn_is_inside_and_infected( + Point { + x: pos.x - 1, + y: pos.y + 1, + }, width, height, humans, infection_rate + ) || //Bottom Left + fn_is_inside_and_infected( + Point { + x: pos.x, + y: pos.y + 1, + }, width, height, humans, infection_rate + ) || //Bottom + fn_is_inside_and_infected( + Point { + x: pos.x + 1, + y: pos.y + 1, + }, width, height, humans, infection_rate + ); //Bottom Right + if infected { + people_to_infect.push(Point { x: pos.x, y: pos.y }); + } + } + people_to_infect + })); + } + for t in threads { + let mut res = t.join().unwrap(); + people_to_infect.append(&mut res); + } + } + + #[cfg(debug_assertions)] + println!("after possible_infected {:?}", timer.elapsed()); + + let mut threads = vec![]; + { let humans = Arc::clone(&self.humans); let width = self.width; @@ -223,7 +333,7 @@ impl Population { for infected_position in people_to_infect.iter() { let infected_index = human_idx(infected_position.x, infected_position.y, width); { - println!("infect"); + // eprint!("i"); let mut humans = humans.write().unwrap(); humans[infected_index].present_state = State::Infected; } @@ -240,10 +350,10 @@ impl Population { let cured_index = human_idx(cured_position.x, cured_position.y, width); { let humans = humans.read().unwrap(); - debug_assert!(humans[cured_index].present_state != State::Infected, "This human should not be infected: {:?}", humans[cured_index].present_state); + debug_assert_eq!(humans[cured_index].present_state, State::Infected, "This human should be infected: {:?}", humans[cured_index].present_state); } { - println!("cure"); + // eprint!("c"); let mut humans = humans.write().unwrap(); humans[cured_index].present_state = State::Immune; } @@ -262,10 +372,10 @@ impl Population { let dead_index = human_idx(dead_position.x, dead_position.y, width); { let humans = humans.read().unwrap(); - debug_assert_eq!(humans[dead_index].present_state, State::Dead); + debug_assert!(humans[dead_index].present_state != State::Dead); } { - println!("kill"); + // eprint!("k"); let mut humans = humans.write().unwrap(); humans[dead_index].present_state = State::Dead; } @@ -277,10 +387,9 @@ impl Population { t.join().unwrap(); } - debug_assert_eq!( - stats[0] + stats[1] + stats[2] + stats[3], - self.size as i32 - ); + #[cfg(debug_assertions)] + println!("after kills and co {:?}", timer.elapsed()); + stats } @@ -311,3 +420,25 @@ pub fn roll(probability: i32) -> bool { false } } + +fn fn_is_inside(pos: &Point, width: i32, height: i32) -> bool { + if (pos.x >= 0) && (pos.x < width) && (pos.y >= 0) && (pos.y < height) { + true + } else { + false + } +} + +fn fn_is_inside_and_infected(point: Point, width: i32, height: i32, humans: &Vec, infection_rate: i32) -> bool { + if fn_is_inside(&point, width, height) { + let idx = human_idx(point.x, point.y, width); + if humans[idx].present_state == State::Infected { + roll(infection_rate) + } else { + false + } + } else { + false + } +} +