From 113e91dcc897337b11b0887b76fbaca708aa7205 Mon Sep 17 00:00:00 2001 From: Rene Luria Date: Thu, 5 May 2022 15:20:54 +0200 Subject: [PATCH] working version remove const no need for RwLock as we don't use mut in thread \o/ --- Cargo.lock | 144 ++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/main.rs | 12 ++- src/population.rs | 209 +++++++++++++++++++++++++++++++--------------- 4 files changed, 297 insertions(+), 69 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..1594e1d 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.1.15", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index b6aed33..4f71c27 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,18 @@ mod prelude { } use prelude::*; +use clap::Parser; + +#[derive(Parser, Debug)] +struct Args { + /// Number of threads + #[clap(short, long, default_value_t = 1)] + threads: usize, +} fn main() { + let args = Args::parse(); + let term = Term::stdout(); term.write_line("********** Rusty Propagation (Console) 2022 **********") .expect("Oops Looks like we have a problem here..."); @@ -29,7 +39,7 @@ fn main() { let mut counter: u32 = 0; loop { counter += 1; - stats = population.propagate_new(); + stats = population.propagate_new(Some(args.threads)); //population.display(); println!( "Normal: {} Infecteds: {} Immunes: {} Deads: {}", diff --git a/src/population.rs b/src/population.rs index cc90998..fd20ec5 100644 --- a/src/population.rs +++ b/src/population.rs @@ -1,10 +1,13 @@ + +use std::sync::Arc; +use std::thread; use crate::prelude::*; pub struct Population { pub start_infected_ratio: i32, pub start_immune_ratio: i32, pub start_dead_ratio: i32, - pub humans: Vec, + pub humans: Arc>, pub width: i32, pub height: i32, pub age: i32, @@ -65,42 +68,91 @@ impl Population { height: height, plague: plague, age: 0, - humans: the_humans, + humans: Arc::new(the_humans), size: size, } } - pub fn propagate_new(&mut self) -> [i32; 4] { - let mut stats: [i32; 4] = [0, 0, 0, 0]; - let mut humans_n_plus_1: Vec = Vec::with_capacity(self.humans.len()); + pub fn propagate_new(&mut self, num_threads: Option) -> [i32; 4] { + let humans = Arc::clone(&self.humans); + let len = humans.len(); - for human in self.humans.iter() { - let mut neighbors: Vec<&Human> = Vec::with_capacity(8); - if human.present_state == State::Normal { - let possible = [ - (human.x - 1, human.y - 1), (human.x, human.y - 1), (human.x + 1, human.y - 1), - (human.x - 1, human.y) , (human.x + 1, human.y), - (human.x - 1, human.y + 1), (human.x, human.y + 1), (human.x + 1, human.y + 1), - ]; - for neigh_coords in possible.iter() { - let neigh_idx = point_to_index(neigh_coords.0, neigh_coords.1, self.width, self.height); - match neigh_idx { - Some(x) => neighbors.push(&self.humans[x]), - None => {}, - } - } - } - let new_human = evolve(human, neighbors, self.plague.infection_rate, self.plague.curing_rate, self.plague.death_rate); - match human.present_state { - State::Normal => { stats[0] += 1; } - State::Infected => { stats[1] += 1; } - State::Immune => { stats[2] += 1; } - State::Dead => { stats[3] += 1; } - } - humans_n_plus_1.push(new_human); + let mut humans_n_plus_1: Vec = Vec::with_capacity(len); + let mut stats: [i32; 4] = [0, 0, 0, 0]; + + let num_threads = num_threads.unwrap_or(1); + if num_threads > 8 { + panic!("too many threads") } - self.humans = humans_n_plus_1; + // number of items per batch + let mut num_items = (len / num_threads) as usize; + if len % num_threads != 0 { + num_items += 1; + } + + let mut threads = vec![]; + + for x in 0..num_threads { + + // find items to process + let lower_bound = x * num_items; + let mut upper_bound = (x + 1) * num_items; + if upper_bound > len { + upper_bound = len; + }; + + // borrow copies of data for thread + let humans = Arc::clone(&self.humans); + let (width, height, infection_rate, curing_rate, death_rate) = + (self.width, self.height, self.plague.infection_rate, self.plague.curing_rate, self.plague.death_rate); + + threads.push(thread::spawn(move || { + + // no write on data + // let humans = humans; + let mut humans_n_plus_1: Vec = Vec::with_capacity(num_items); + let mut stats: [i32; 4] = [0, 0, 0, 0]; + + for idx in lower_bound..upper_bound { + let human = &humans[idx]; + let mut neighbors: Vec<&Human> = Vec::with_capacity(8); + if human.present_state == State::Normal { + let possible = [ + (human.x - 1, human.y - 1), (human.x, human.y - 1), (human.x + 1, human.y - 1), + (human.x - 1, human.y) , (human.x + 1, human.y), + (human.x - 1, human.y + 1), (human.x, human.y + 1), (human.x + 1, human.y + 1), + ]; + for neigh_coords in possible.iter() { + let neigh_idx = point_to_index(neigh_coords.0, neigh_coords.1, width, height); + match neigh_idx { + Some(x) => neighbors.push(&humans[x]), + None => {}, + } + } + } + let new_human = evolve(human, neighbors, infection_rate, curing_rate, death_rate); + match human.present_state { + State::Normal => { stats[0] += 1; } + State::Infected => { stats[1] += 1; } + State::Immune => { stats[2] += 1; } + State::Dead => { stats[3] += 1; } + } + humans_n_plus_1.push(new_human); + } + (humans_n_plus_1, stats) + })); + } + + for t in threads { + let mut res = t.join().unwrap(); + humans_n_plus_1.append(&mut res.0); + for x in 0..4 { + stats[x] += res.1[x]; + } + } + + self.humans = Arc::new(humans_n_plus_1); stats } } @@ -163,6 +215,8 @@ mod tests { use super::*; use parameterized::parameterized; + const THREADS: Option = Some(4); + #[derive(Debug)] struct Stats { normal: i32, @@ -251,14 +305,14 @@ mod tests { fn population_new() { let disease = Disease::new(20, 10, 5, String::from("Covid 44")); let (width, height) = (5, 7); - let population = Population::new(20, 10, 5, 5, 7, disease); - assert_eq!(population.humans.len(), 5 * 7); - for h in population.humans.iter() { + let population = Population::new(20, 10, 5, width, height, disease); + let humans = Arc::clone(&population.humans); + assert_eq!(humans.len(), 5 * 7); + for h in humans.iter() { let idx = human_idx(h.x, h.y, width); - assert_eq!(population.humans[idx].x, h.x, "coordinates should match"); - assert_eq!(population.humans[idx].y, h.y, "coordinates should match"); + assert_eq!(humans[idx].x, h.x, "coordinates should match"); + assert_eq!(humans[idx].y, h.y, "coordinates should match"); } - assert_eq!(population.humans.len(), (width * height) as usize); } #[test] @@ -267,7 +321,8 @@ mod tests { let (width, height) = (5, 7); let population = Population::new(20, 10, 5, 5, 7, disease); - let stats: Stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + let stats: Stats = humans_stats(&humans); println!("Stats: {:?}", stats); assert_eq!( @@ -285,25 +340,29 @@ mod tests { disease = Disease::new(0, 0, 0, String::from("Test")); population = Population::new(0, 0, 0, width, height, disease); - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("should be normal: {:?}", stats); assert_eq!(stats.normal, width * height); disease = Disease::new(0, 0, 0, String::from("Test")); population = Population::new(100, 0, 0, width, height, disease); - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("should be infected: {:?}", stats); assert_eq!(stats.infected, width * height); disease = Disease::new(0, 0, 0, String::from("Test")); population = Population::new(0, 100, 0, width, height, disease); - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("should be immune: {:?}", stats); assert_eq!(stats.immune, width * height); disease = Disease::new(0, 0, 0, String::from("Test")); population = Population::new(0, 0, 100, width, height, disease); - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("should be dead: {:?}", stats); assert_eq!(stats.dead, width * height); } @@ -329,13 +388,15 @@ mod tests { let mut propagate_stats: [i32; 4]; // infect every one - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("stats after init: {:?}", stats); assert_eq!(stats.infected, 100, "everybody should be infected"); // kill every one - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); assert_eq!(propagate_stats, [0, 100, 0, 0]); assert_eq!(stats.normal, 0); @@ -344,8 +405,9 @@ mod tests { assert_eq!(stats.dead, 100); for _x in 0..100 { - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); assert_eq!(propagate_stats, [0, 0, 0, 100]); assert_eq!(stats.normal, 0); @@ -363,7 +425,7 @@ mod tests { let mut propagate_stats: [i32; 4]; // start with normal population - population.humans = vec![ + population.humans = Arc::new(vec![ Human { present_state: State::Normal, x: 0, @@ -409,14 +471,16 @@ mod tests { x: 2, y: 2, }, - ]; - stats = humans_stats(&population.humans); + ]); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("stats after init: {:?}", stats); assert_eq!(stats.normal, 8); // kill every one - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); assert_eq!(propagate_stats, [8, 1, 0, 0]); assert_eq!(stats.normal, 0); @@ -425,8 +489,9 @@ mod tests { assert_eq!(stats.dead, 0); for _x in 0..100 { - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); assert_eq!(propagate_stats, [0, 9, 0, 0]); assert_eq!(stats.normal, 0); @@ -444,7 +509,7 @@ mod tests { let mut propagate_stats: [i32; 4]; // start with normal population - population.humans = vec![ + population.humans = Arc::new(vec![ Human { present_state: State::Normal, x: 0, @@ -490,14 +555,16 @@ mod tests { x: 2, y: 2, }, - ]; - stats = humans_stats(&population.humans); + ]); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("stats after init: {:?}", stats); assert_eq!(stats.normal, 8); // infect every one - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); println!("population: {:?}", stats); assert_eq!(propagate_stats, [8, 1, 0, 0]); @@ -507,8 +574,9 @@ mod tests { assert_eq!(stats.dead, 0); // cure every one - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); println!("population: {:?}", stats); assert_eq!(propagate_stats, [0, 8, 1, 0]); @@ -519,8 +587,9 @@ mod tests { // then for _x in 0..100 { - propagate_stats = population.propagate_new(); - stats = humans_stats(&population.humans); + propagate_stats = population.propagate_new(THREADS); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("propate_stats: {:?}", propagate_stats); println!("population: {:?}", stats); assert_eq!(propagate_stats, [0, 0, 9, 0]); @@ -539,16 +608,18 @@ mod tests { let stats_before: Stats; let stats_after: Stats; - stats_before = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats_before = humans_stats(&humans); let should_be_infected = population.size as i32 * infection_start / 100; let tolerance = (should_be_infected as f32 * 0.20) as i32; println!("{:?}", stats_before); assert!(stats_before.infected <= should_be_infected + tolerance, "{} infected, should be less than {}", stats_before.infected, should_be_infected + tolerance); assert!(stats_before.infected >= should_be_infected - tolerance, "{} infected, should be more than {}", stats_before.infected, should_be_infected - tolerance); - population.propagate_new(); + population.propagate_new(THREADS); - stats_after = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats_after = humans_stats(&humans); assert_eq!(stats_before.infected, stats_after.infected, "no one should have been infected"); } @@ -568,7 +639,8 @@ mod tests { disease = Disease::new(infection_rate, 0, death_rate, String::from("Test")); population = Population::new(start_infected, 0, 0, width, height, disease); - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("Population after init: {:?}", stats); // total * proba - 20% < infected < total * proba + 20% @@ -581,7 +653,7 @@ mod tests { let infected_at_start = stats.infected; let dead_at_start = stats.dead; - let propa_stats: [i32; 4] = population.propagate_new(); + let propa_stats: [i32; 4] = population.propagate_new(THREADS); assert!(propa_stats[3] >= dead_at_start); @@ -589,7 +661,8 @@ mod tests { assert_eq!(propa_stats[3], 0, "no human should have died"); } - stats = humans_stats(&population.humans); + let humans = Arc::clone(&population.humans); + stats = humans_stats(&humans); println!("Population after propagate: {:?}", stats); assert!(stats.infected <= infected_at_start + width * height * infected_expected);