switch to fastrand

This commit is contained in:
Joseph Montanaro 2023-01-02 21:21:02 -08:00
parent 73f70e4fa9
commit 0c7bbbe200
5 changed files with 63 additions and 87 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
*.etl*

63
Cargo.lock generated
View File

@ -13,7 +13,7 @@ name = "cup"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"enum-map", "enum-map",
"rand", "fastrand",
] ]
[[package]] [[package]]
@ -37,27 +37,22 @@ dependencies = [
] ]
[[package]] [[package]]
name = "getrandom" name = "fastrand"
version = "0.2.8" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
dependencies = [ dependencies = [
"cfg-if", "instant",
"libc",
"wasi",
] ]
[[package]] [[package]]
name = "libc" name = "instant"
version = "0.2.139" version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
[[package]] "cfg-if",
name = "ppv-lite86" ]
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -77,36 +72,6 @@ dependencies = [
"proc-macro2", "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]] [[package]]
name = "syn" name = "syn"
version = "1.0.107" version = "1.0.107"
@ -123,9 +88,3 @@ name = "unicode-ident"
version = "1.0.6" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"

View File

@ -6,5 +6,12 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
"enum-map" = "2.4.2" enum-map = "2.4.2"
"rand" = "0.8.5" fastrand = "1.8.0"
# [profile.release]
# lto = true
[profile.perf]
inherits = "release"
debug = true

View File

@ -1,9 +1,5 @@
use enum_map::{Enum, EnumMap}; use enum_map::{Enum, EnumMap};
use rand::{ use fastrand::Rng;
Rng,
seq::SliceRandom,
distributions::{Distribution, Uniform},
};
use crate::stack::Stack; use crate::stack::Stack;
@ -84,11 +80,11 @@ impl Game {
// new game with random starting positions // new game with random starting positions
pub fn new_random() -> Self { pub fn new_random() -> Self {
let mut game = Self::default(); let mut game = Self::default();
let mut rng = rand::thread_rng(); let rng = Rng::new();
let mut dice = *&COLORS; let mut dice = *&COLORS;
dice.shuffle(&mut rng); rng.shuffle(&mut dice);
for color in 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.squares[roll - 1].assume_stack_mut().push(color);
game.camels[color] = roll - 1; game.camels[color] = roll - 1;
} }
@ -187,8 +183,7 @@ impl Game {
None None
} }
fn finish_leg_random(&mut self) -> Option<Color> { fn finish_leg_random(&mut self, rng: &Rng) -> Option<Color> {
let mut rng = rand::thread_rng();
let mut leg_dice: Stack<Color, 5> = Stack::new(); let mut leg_dice: Stack<Color, 5> = Stack::new();
for (color, rolled) in self.dice { for (color, rolled) in self.dice {
if !rolled { 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() { 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) { if let Some(winner) = self.advance(*color, roll) {
return Some(winner); return Some(winner);
} }
@ -206,22 +201,19 @@ impl Game {
None None
} }
fn finish_game_random(&mut self) -> Color { fn finish_game_random(&mut self, rng: &Rng) -> Color {
if let Some(winner) = self.finish_leg_random() { if let Some(winner) = self.finish_leg_random(rng) {
return winner; 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, // we are now guaranteed to be at the start of a new leg,
// so we don't need to check the dice state // 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 { loop {
// easiest if we shuffle at the start of the leg // easiest if we shuffle at the start of the leg
dice.shuffle(&mut rng); rng.shuffle(&mut dice);
for i in 0..5 { 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) { if let Some(winner) = self.advance(dice[i], roll) {
return winner; return winner;
} }
@ -234,8 +226,9 @@ impl Game {
let mut projection = *self; let mut projection = *self;
let mut scores: EnumMap<Color, usize> = EnumMap::default(); let mut scores: EnumMap<Color, usize> = EnumMap::default();
let rng = Rng::new();
for i in 0..count { for i in 0..count {
let winner = projection.finish_game_random(); let winner = projection.finish_game_random(&rng);
scores[winner] += 1; scores[winner] += 1;
projection.set_state(&orig_camels, &orig_dice); projection.set_state(&orig_camels, &orig_dice);
} }
@ -324,7 +317,8 @@ mod test {
for _ in 0..100 { for _ in 0..100 {
let mut projection = game; // copy? let mut projection = game; // copy?
assert_eq!(projection.squares[1].assume_stack(), &Stack::from([Purple, Blue])); 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 // since we already rolled Green, it can't have moved
assert_eq!(projection.camels[Green], 3); assert_eq!(projection.camels[Green], 3);
// likewise purple // likewise purple
@ -349,7 +343,8 @@ mod test {
game.set_state(&camel_state, &Default::default()); game.set_state(&camel_state, &Default::default());
// since there are no tiles involved, and multiple camels are on 15, there must be a winner // 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] #[test]

View File

@ -3,24 +3,38 @@ use std::time::Instant;
mod stack; mod stack;
mod game; mod game;
use game::Game; use game::{Game, Color::*};
fn main() { fn main() {
let n_games = 10_000_000; let n_games = 200_000;
let start = Instant::now();
let game = Game::new_random(); 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 end = Instant::now();
let elapsed = end.duration_since(start); let elapsed = end.duration_since(start);
let secs = elapsed.as_secs(); let secs = (elapsed.as_secs_f64() * 100f64).round() / 100f64;
let hundredths = elapsed.subsec_millis() / 10; // technically not accurate but good enough for now let rate = (n_games as f64) / elapsed.as_secs_f64();
let rate = (10_000_000 as f64) / elapsed.as_secs_f64();
println!("Test completed:"); println!("Test completed:");
println!("{n_games} in {secs}.{hundredths:02} seconds", ); println!("{n_games} in {secs} seconds", );
println!("Games per second: {rate}"); println!("Games per second: {rate}\n");
let total = scores.values().sum::<usize>() as f64;
for (color, score) in scores {
let fract = (score as f64) / total;
let pct = (fract * 10_000f64).round() / 100f64;
println!("{color:?}: {pct}%");
}
} }