add simulation benchmarks
All checks were successful
Build / build (push) Successful in 2m7s

This commit is contained in:
Max Känner 2025-01-21 19:04:30 +01:00
parent 1410602c6c
commit 2f2b7ac11e
7 changed files with 578 additions and 24 deletions

308
Cargo.lock generated
View File

@ -26,6 +26,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.18"
@ -173,6 +179,7 @@ version = "2.0.0"
dependencies = [
"axum",
"bitvec",
"criterion",
"enum-iterator",
"env_logger",
"log",
@ -202,6 +209,12 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "byteorder"
version = "1.5.0"
@ -214,12 +227,70 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
[[package]]
name = "cast"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "ciborium"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
"ciborium-io",
"ciborium-ll",
"serde",
]
[[package]]
name = "ciborium-io"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
[[package]]
name = "ciborium-ll"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
dependencies = [
"ciborium-io",
"half",
]
[[package]]
name = "clap"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
@ -235,6 +306,42 @@ dependencies = [
"libc",
]
[[package]]
name = "criterion"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"is-terminal",
"itertools",
"num-traits",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"walkdir",
]
[[package]]
name = "criterion-plot"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [
"cast",
"itertools",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
@ -260,6 +367,12 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -458,12 +571,28 @@ dependencies = [
"tracing",
]
[[package]]
name = "half"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
dependencies = [
"cfg-if",
"crunchy",
]
[[package]]
name = "hashbrown"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "hermit-abi"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "http"
version = "1.2.0"
@ -562,18 +691,48 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "is-terminal"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.169"
@ -641,6 +800,15 @@ dependencies = [
"version_check",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
@ -656,6 +824,12 @@ version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oorandom"
version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
[[package]]
name = "percent-encoding"
version = "2.3.1"
@ -674,6 +848,34 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "plotters"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "plotters-backend"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@ -804,6 +1006,15 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.217"
@ -943,6 +1154,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tinytemplate"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "tokio"
version = "1.43.0"
@ -1091,12 +1312,99 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "web-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "windows-sys"
version = "0.52.0"

View File

@ -29,3 +29,10 @@ env_logger = "0.11"
bitvec = "1.0"
enum-iterator = "2.1"
rand = "0.8"
[dev-dependencies]
criterion = "0.5"
[[bench]]
name = "simulation"
harness = false

View File

@ -0,0 +1,220 @@
use std::sync::atomic::{AtomicU32, Ordering};
use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion};
use battlesnake::types::{
simulation::Board,
wire::{Battlesnake, Board as WireBoard, Game, Request, RoyaleSettings, Ruleset, Settings},
Coord,
};
fn create_start_snake(coord: Coord) -> Battlesnake {
let id = format!("{coord:?}");
Battlesnake {
id: id.clone(),
name: id.clone(),
health: 100,
body: vec![coord; 3],
latency: "0".into(),
head: coord,
length: 3,
shout: String::new(),
squad: id,
}
}
fn create_standard_start_request(starts: [Coord; 4]) -> Request {
Request {
game: Game {
id: "test".into(),
ruleset: Ruleset {
name: "standard".into(),
version: "0".into(),
settings: Settings {
food_spawn_chance: 15,
minimum_food: 1,
hazard_damage_per_turn: 30,
royale: RoyaleSettings {
shrink_every_n_turns: 20,
},
},
},
map: "standard".into(),
timeout: 500,
source: "other".into(),
},
turn: 0,
board: WireBoard {
height: 11,
width: 11,
food: vec![Coord { x: 5, y: 5 }],
hazards: vec![],
snakes: vec![
create_start_snake(starts[0]),
create_start_snake(starts[1]),
create_start_snake(starts[2]),
create_start_snake(starts[3]),
],
},
you: create_start_snake(starts[0]),
}
}
fn standard(c: &mut Criterion) {
let turns_min = AtomicU32::new(u32::MAX);
let turns_max = AtomicU32::new(u32::MIN);
let turns_sum = AtomicU32::new(0);
let turns_total = AtomicU32::new(0);
let mut group = c.benchmark_group("standard");
group.sample_size(10000);
let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| {
let mut board = board.clone();
let turn = board.simulate_random(|board| {
if board.num_snakes() <= 1 {
Some(board.turn())
} else {
None
}
});
if turn < turns_min.load(Ordering::Relaxed) {
turns_min.store(turn, Ordering::Relaxed);
}
if turn > turns_max.load(Ordering::Relaxed) {
turns_max.store(turn, Ordering::Relaxed);
}
turns_sum.fetch_add(turn, Ordering::Relaxed);
turns_total.fetch_add(1, Ordering::Relaxed);
});
};
let request = create_standard_start_request([
Coord { x: 1, y: 1 },
Coord { x: 9, y: 1 },
Coord { x: 1, y: 9 },
Coord { x: 9, y: 9 },
]);
let board = Board::from(&request);
group.bench_with_input(
BenchmarkId::from_parameter("start x"),
black_box(&board),
benchmark,
);
{
let max = turns_max.load(Ordering::Relaxed);
let min = turns_min.load(Ordering::Relaxed);
let sum = turns_sum.load(Ordering::Relaxed);
let total = turns_total.load(Ordering::Relaxed);
let avg = sum / total;
println!("turns: [{min}, {max}] avg {avg} @ {total} samples");
turns_max.store(u32::MIN, Ordering::Relaxed);
turns_min.store(u32::MAX, Ordering::Relaxed);
turns_sum.store(0, Ordering::Relaxed);
turns_total.store(0, Ordering::Relaxed);
}
let request = create_standard_start_request([
Coord { x: 5, y: 1 },
Coord { x: 1, y: 5 },
Coord { x: 5, y: 9 },
Coord { x: 9, y: 5 },
]);
let board = Board::from(&request);
group.bench_with_input(
BenchmarkId::from_parameter("start +"),
black_box(&board),
benchmark,
);
{
let max = turns_max.load(Ordering::Relaxed);
let min = turns_min.load(Ordering::Relaxed);
let sum = turns_sum.load(Ordering::Relaxed);
let total = turns_total.load(Ordering::Relaxed);
let avg = sum / total;
println!("turns: [{min}, {max}] avg {avg} @ {total} samples");
}
}
fn constrictor(c: &mut Criterion) {
let turns_min = AtomicU32::new(u32::MAX);
let turns_max = AtomicU32::new(u32::MIN);
let turns_sum = AtomicU32::new(0);
let turns_total = AtomicU32::new(0);
let mut group = c.benchmark_group("constrictor");
group.sample_size(10000);
let benchmark = |b: &mut Bencher, board: &Board| {
b.iter(|| {
let mut board = board.clone();
let turn = board.simulate_random(|board| {
if board.num_snakes() <= 1 {
Some(board.turn())
} else {
None
}
});
if turn < turns_min.load(Ordering::Relaxed) {
turns_min.store(turn, Ordering::Relaxed);
}
if turn > turns_max.load(Ordering::Relaxed) {
turns_max.store(turn, Ordering::Relaxed);
}
turns_sum.fetch_add(turn, Ordering::Relaxed);
turns_total.fetch_add(1, Ordering::Relaxed);
});
};
let mut request = create_standard_start_request([
Coord { x: 1, y: 1 },
Coord { x: 9, y: 1 },
Coord { x: 1, y: 9 },
Coord { x: 9, y: 9 },
]);
request.game.ruleset.name = "constrictor".into();
let board = Board::from(&request);
group.bench_with_input(
BenchmarkId::from_parameter("start x"),
black_box(&board),
benchmark,
);
{
let max = turns_max.load(Ordering::Relaxed);
let min = turns_min.load(Ordering::Relaxed);
let sum = turns_sum.load(Ordering::Relaxed);
let total = turns_total.load(Ordering::Relaxed);
let avg = sum / total;
println!("turns: [{min}, {max}] avg {avg} @ {total} samples");
turns_max.store(u32::MIN, Ordering::Relaxed);
turns_min.store(u32::MAX, Ordering::Relaxed);
turns_sum.store(0, Ordering::Relaxed);
turns_total.store(0, Ordering::Relaxed);
}
let mut request = create_standard_start_request([
Coord { x: 5, y: 1 },
Coord { x: 1, y: 5 },
Coord { x: 5, y: 9 },
Coord { x: 9, y: 5 },
]);
request.game.ruleset.name = "constrictor".into();
let board = Board::from(&request);
group.bench_with_input(
BenchmarkId::from_parameter("start +"),
black_box(&board),
benchmark,
);
{
let max = turns_max.load(Ordering::Relaxed);
let min = turns_min.load(Ordering::Relaxed);
let sum = turns_sum.load(Ordering::Relaxed);
let total = turns_total.load(Ordering::Relaxed);
let avg = sum / total;
println!("turns: [{min}, {max}] avg {avg} @ {total} samples");
}
}
criterion_group!(benches, standard, constrictor);
criterion_main!(benches);

1
battlesnake/src/lib.rs Normal file
View File

@ -0,0 +1 @@
pub mod types;

View File

@ -1,20 +1,18 @@
mod types;
use axum::{
extract::Json,
response,
routing::{get, post},
Router,
};
use log::{debug, info, warn};
use rand::prelude::*;
use serde::Serialize;
use tokio::{net::TcpListener, time::Instant};
use types::{
use battlesnake::types::{
simulation::Board,
wire::{Request, Response},
Direction,
};
use log::{debug, info, warn};
use rand::prelude::*;
use serde::Serialize;
use tokio::{net::TcpListener, time::Instant};
#[tokio::main]
async fn main() {

View File

@ -6,11 +6,12 @@ pub mod wire;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Deserialize)]
pub struct Coord {
x: u8,
y: u8,
pub x: u8,
pub y: u8,
}
impl Coord {
#[must_use]
pub fn apply(self, direction: Direction) -> Option<Self> {
match direction {
Direction::Up => self.y.checked_add(1).map(|y| Self { y, x: self.x }),
@ -20,7 +21,8 @@ impl Coord {
}
}
pub fn wrapping_apply(mut self, direction: Direction) -> Self {
#[must_use]
pub const fn wrapping_apply(mut self, direction: Direction) -> Self {
match direction {
Direction::Up => self.y = self.y.wrapping_add(1),
Direction::Down => self.y = self.y.wrapping_sub(1),

View File

@ -121,20 +121,29 @@ impl Display for Board {
}
impl Board {
#[must_use]
pub const fn turn(&self) -> u32 {
self.turn
}
#[must_use]
pub fn num_snakes(&self) -> usize {
self.snakes.len()
}
#[must_use]
pub fn is_food(&self, tile: Coord) -> bool {
let index = self.coord_to_linear(tile);
self.food[index]
}
#[must_use]
pub fn is_hazard(&self, tile: Coord) -> bool {
let index = self.coord_to_linear(tile);
self.hazard[index]
}
#[must_use]
pub fn is_free(&self, tile: Coord) -> bool {
if !(tile.x < self.width && tile.y < self.height) {
return false;
@ -182,6 +191,7 @@ impl Board {
self.eliminate_snake_standard();
self.update_free_map();
self.spawn_food();
self.turn += 1;
}
fn move_standard(&mut self, actions: &[(u8, Direction)]) {
@ -232,6 +242,13 @@ impl Board {
}
fn feed_snakes_standard(&mut self) {
if self.constrictor {
for snake in &mut self.snakes {
snake.health = 100;
let tail = snake.tail();
snake.body.push_back(tail);
}
} else {
let mut eaten_food = vec![];
for i in 0..self.snakes.len() {
let head = self.snakes[i].head();
@ -250,6 +267,7 @@ impl Board {
self.food.set(food_index, false);
}
}
}
fn eliminate_snake_standard(&mut self) {
// eliminate out of health and out of bounds