From 0c7bbbe200ac0f0d4c926d99419facbf775a8299 Mon Sep 17 00:00:00 2001 From: Joseph Montanaro Date: Mon, 2 Jan 2023 21:21:02 -0800 Subject: [PATCH] switch to fastrand --- .gitignore | 1 + Cargo.lock | 63 ++++++++++------------------------------------------- Cargo.toml | 11 ++++++++-- src/game.rs | 41 +++++++++++++++------------------- src/main.rs | 34 ++++++++++++++++++++--------- 5 files changed, 63 insertions(+), 87 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..9f85ec9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.etl* diff --git a/Cargo.lock b/Cargo.lock index 92bfa5d..be309ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ name = "cup" version = "0.1.0" dependencies = [ "enum-map", - "rand", + "fastrand", ] [[package]] @@ -37,27 +37,22 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.8" +name = "fastrand" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ - "cfg-if", - "libc", - "wasi", + "instant", ] [[package]] -name = "libc" -version = "0.2.139" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] [[package]] name = "proc-macro2" @@ -77,36 +72,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - [[package]] name = "syn" version = "1.0.107" @@ -123,9 +88,3 @@ name = "unicode-ident" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml index 04d0d45..4f0bd10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,5 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -"enum-map" = "2.4.2" -"rand" = "0.8.5" +enum-map = "2.4.2" +fastrand = "1.8.0" + +# [profile.release] +# lto = true + +[profile.perf] +inherits = "release" +debug = true diff --git a/src/game.rs b/src/game.rs index e4b4851..cbfeb56 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,9 +1,5 @@ use enum_map::{Enum, EnumMap}; -use rand::{ - Rng, - seq::SliceRandom, - distributions::{Distribution, Uniform}, -}; +use fastrand::Rng; use crate::stack::Stack; @@ -84,11 +80,11 @@ impl Game { // new game with random starting positions pub fn new_random() -> Self { let mut game = Self::default(); - let mut rng = rand::thread_rng(); + let rng = Rng::new(); let mut dice = *&COLORS; - dice.shuffle(&mut rng); + rng.shuffle(&mut dice); for color in dice { - let roll = rng.gen_range(1..=3); + let roll = rng.usize(1..=3); game.squares[roll - 1].assume_stack_mut().push(color); game.camels[color] = roll - 1; } @@ -187,8 +183,7 @@ impl Game { None } - fn finish_leg_random(&mut self) -> Option { - let mut rng = rand::thread_rng(); + fn finish_leg_random(&mut self, rng: &Rng) -> Option { let mut leg_dice: Stack = Stack::new(); for (color, rolled) in self.dice { if !rolled { @@ -196,9 +191,9 @@ impl Game { } } - (&mut leg_dice[..]).shuffle(&mut rng); + rng.shuffle(&mut leg_dice[..]); for color in leg_dice.iter() { - let roll = rng.gen_range(1..=3); + let roll = rng.usize(1..=3); if let Some(winner) = self.advance(*color, roll) { return Some(winner); } @@ -206,22 +201,19 @@ impl Game { None } - fn finish_game_random(&mut self) -> Color { - if let Some(winner) = self.finish_leg_random() { + fn finish_game_random(&mut self, rng: &Rng) -> Color { + if let Some(winner) = self.finish_leg_random(rng) { return winner; } + let mut dice = COLORS; // makes a copy of the constant // we are now guaranteed to be at the start of a new leg, // so we don't need to check the dice state - let mut rng = rand::thread_rng(); - let roll_dist = Uniform::from(1..=3); - let mut dice = COLORS; // makes a copy of the constant - loop { // easiest if we shuffle at the start of the leg - dice.shuffle(&mut rng); + rng.shuffle(&mut dice); for i in 0..5 { - let roll = roll_dist.sample(&mut rng); + let roll = rng.usize(1..=3); if let Some(winner) = self.advance(dice[i], roll) { return winner; } @@ -234,8 +226,9 @@ impl Game { let mut projection = *self; let mut scores: EnumMap = EnumMap::default(); + let rng = Rng::new(); for i in 0..count { - let winner = projection.finish_game_random(); + let winner = projection.finish_game_random(&rng); scores[winner] += 1; projection.set_state(&orig_camels, &orig_dice); } @@ -324,7 +317,8 @@ mod test { for _ in 0..100 { let mut projection = game; // copy? assert_eq!(projection.squares[1].assume_stack(), &Stack::from([Purple, Blue])); - projection.finish_leg_random(); + let rng = Rng::new(); + projection.finish_leg_random(&rng); // since we already rolled Green, it can't have moved assert_eq!(projection.camels[Green], 3); // likewise purple @@ -349,7 +343,8 @@ mod test { game.set_state(&camel_state, &Default::default()); // since there are no tiles involved, and multiple camels are on 15, there must be a winner - assert!(matches!(game.finish_leg_random(), Some(_))); + let rng = Rng::new(); + assert!(matches!(game.finish_leg_random(&rng), Some(_))); } #[test] diff --git a/src/main.rs b/src/main.rs index e1ab2d4..75ade67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,24 +3,38 @@ use std::time::Instant; mod stack; mod game; -use game::Game; +use game::{Game, Color::*}; fn main() { - let n_games = 10_000_000; + let n_games = 200_000; - let start = Instant::now(); let game = Game::new_random(); - let _scores = game.project_outcomes(n_games); + // let mut game = Game::new(); + // let camel_state = [ + // (Blue, 5), + // (Purple, 5), + // (Red, 7), + // (Yellow, 8), + // (Green, 10), + // ]; + // game.set_state(&camel_state, &Default::default()); + let start = Instant::now(); + let scores = game.project_outcomes(n_games); let end = Instant::now(); let elapsed = end.duration_since(start); - let secs = elapsed.as_secs(); - let hundredths = elapsed.subsec_millis() / 10; // technically not accurate but good enough for now - - let rate = (10_000_000 as f64) / elapsed.as_secs_f64(); + let secs = (elapsed.as_secs_f64() * 100f64).round() / 100f64; + let rate = (n_games as f64) / elapsed.as_secs_f64(); println!("Test completed:"); - println!("{n_games} in {secs}.{hundredths:02} seconds", ); - println!("Games per second: {rate}"); + println!("{n_games} in {secs} seconds", ); + println!("Games per second: {rate}\n"); + + let total = scores.values().sum::() as f64; + for (color, score) in scores { + let fract = (score as f64) / total; + let pct = (fract * 10_000f64).round() / 100f64; + println!("{color:?}: {pct}%"); + } }