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
*.etl*

63
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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<Color> {
let mut rng = rand::thread_rng();
fn finish_leg_random(&mut self, rng: &Rng) -> Option<Color> {
let mut leg_dice: Stack<Color, 5> = 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<Color, usize> = 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]

View File

@ -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::<usize>() as f64;
for (color, score) in scores {
let fract = (score as f64) / total;
let pct = (fract * 10_000f64).round() / 100f64;
println!("{color:?}: {pct}%");
}
}