allocator optimizations

This commit is contained in:
Simon Gardling 2025-02-10 01:33:21 -05:00
parent 598c38efd6
commit 3d3eb01143
Signed by: titaniumtown
GPG Key ID: 9AB28AC10ECE533D
5 changed files with 41 additions and 50 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
/target
/flamegraph.svg
/perf.data*

View File

@ -136,6 +136,9 @@ impl Board {
/// Propegate piece captures originating from (i, j)
/// DO NOT USE THIS ALONE, this should be called as a part of
/// [`Board::place`] or [`Board::place_and_prop_unchecked`]
// TODO! this function is responsible for approx 64% of the time spent computing moves in `ComplexAgent`
// IDEAS: early-exit from each chain so we don't have to call `diag` (which allocs a lot and uses a lot of cycles)
// NOTE! got it down to 24.86% (61.1% decrease) with allocator optimizations
fn propegate_from_dry(&self, i: usize, j: usize, starting_color: Piece) -> Vec<(usize, usize)> {
// Create all chains from the piece being propegated from in `i` and `j` coordinates
let (i_chain, j_chain) = (
@ -143,21 +146,16 @@ impl Board {
split_from(0, BOARD_SIZE - 1, j),
);
let mut chains: Vec<Vec<(usize, usize)>> = i_chain
.into_iter()
.map(|range| range.into_iter().map(|i| (i, j)).collect())
.collect();
let mut chains: Vec<Vec<(usize, usize)>> = Vec::with_capacity(8);
chains.extend(
j_chain
.into_iter()
.map(|range| range.into_iter().map(|j| (i, j)).collect()),
);
chains.extend(i_chain.map(|range| range.into_iter().map(|i| (i, j)).collect()));
chains.extend(j_chain.map(|range| range.into_iter().map(|j| (i, j)).collect()));
// handle diagonals
chains.extend(diag(i, j, 0, 0, BOARD_SIZE - 1, BOARD_SIZE - 1));
let mut fill: Vec<(usize, usize)> = Vec::new();
let mut fill: Vec<(usize, usize)> = Vec::with_capacity(chains.iter().map(Vec::len).sum());
for chain in chains {
for (chain_length, &(new_i, new_j)) in chain.iter().enumerate() {
@ -167,15 +165,14 @@ impl Board {
};
if piece == &starting_color {
// fill all opposite colors with this color
let Some(history) = chain.get(..chain_length) else {
break;
};
// fill all opposite colors with this color
for &(i_o, j_o) in history {
fill.push((i_o, j_o));
// get history of this chain
if let Some(history) = chain.get(..chain_length) {
// fill all opposite colors with this color
for &(i_o, j_o) in history {
fill.push((i_o, j_o));
}
}
// either the other pieces were replaced, or this was an invalid chain,
// in both cases, the loop needs to be breaked
break;

View File

@ -58,7 +58,6 @@ struct FutureMoves {
max_depth: usize,
/// Color w.r.t
agent_color: Piece,
gc: usize,
}
impl FutureMoves {
@ -69,7 +68,6 @@ impl FutureMoves {
current_depth: 0,
max_depth,
agent_color,
gc: 0,
}
}
@ -205,10 +203,7 @@ impl FutureMoves {
if let Some(root) = new_root {
self.current_root = Some(root);
self.current_depth = self.max_depth - self.depth_of(Some(root));
self.gc += 1;
if self.gc > 3 {
self.prune_unrelated();
}
self.prune_unrelated();
true
} else {
false

View File

@ -11,8 +11,8 @@ mod piece;
fn main() {
let player1 = complexagent::ComplexAgent::new(Piece::Black);
// let player2 = complexagent::ComplexAgent::new(Piece::White);
let player2 = agent::ManualAgent::new(Piece::White);
// let player2 = agent::RandomAgent::new(Piece::White);
// let player2 = agent::ManualAgent::new(Piece::White);
let player2 = agent::RandomAgent::new(Piece::White);
let mut game = Game::new(Box::new(player1), Box::new(player2));
game.game_loop();
}

View File

@ -1,33 +1,30 @@
use std::{iter::Rev, ops::RangeInclusive};
pub fn split_from<T>(min: T, max: T, x: T) -> Vec<Vec<T>>
pub fn split_from<T>(min: T, max: T, x: T) -> [Vec<T>; 2]
where
T: num::Integer + Copy,
RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
Rev<RangeInclusive<T>>: Iterator<Item = T>,
{
let mut output = [const { Vec::new() }; 2];
// check that x is in range
if min > x || x > max {
return Vec::new();
return output;
}
let mut output: Vec<Vec<T>> = Vec::with_capacity(2);
if x > min + T::one() {
let x_lower = x - T::one();
output.push((min..=x_lower).rev().collect());
} else {
output.push(Vec::new());
output[0] = (min..=x_lower).rev().collect();
}
if x + T::one() < max {
output.push(((x + T::one())..=max).collect());
} else {
output.push(Vec::new());
output[1] = ((x + T::one())..=max).collect();
}
output
}
pub fn diag<T>(i: T, j: T, min_i: T, min_j: T, max_i: T, max_j: T) -> Vec<Vec<(T, T)>>
pub fn diag<T>(i: T, j: T, min_i: T, min_j: T, max_i: T, max_j: T) -> [Vec<(T, T)>; 4]
where
T: num::Integer + Copy,
RangeInclusive<T>: Iterator<Item = T> + DoubleEndedIterator,
@ -36,25 +33,25 @@ where
let i_chains = split_from(min_i, max_i, i);
let j_chains = split_from(min_j, max_j, j);
vec![
[
i_chains[0]
.clone()
.into_iter()
.iter()
.cloned()
.zip(j_chains[0].clone())
.collect(),
i_chains[1]
.clone()
.into_iter()
.iter()
.cloned()
.zip(j_chains[1].clone())
.collect(),
i_chains[1]
.clone()
.into_iter()
.iter()
.cloned()
.zip(j_chains[0].clone())
.collect(),
i_chains[0]
.clone()
.into_iter()
.iter()
.cloned()
.zip(j_chains[1].clone())
.collect(),
]
@ -66,16 +63,16 @@ mod test {
#[test]
fn split_test() {
assert_eq!(split_from(0, 6, 2), vec![vec![1, 0], vec![3, 4, 5, 6]]);
assert_eq!(split_from(0, 6, 2), [vec![1, 0], vec![3, 4, 5, 6]]);
assert_eq!(split_from(0, 6, 0), vec![vec![], vec![1, 2, 3, 4, 5, 6]]);
assert_eq!(split_from(0, 6, 0), [vec![], vec![1, 2, 3, 4, 5, 6]]);
assert_eq!(split_from(0, 6, 6), vec![vec![5, 4, 3, 2, 1, 0], vec![]]);
assert_eq!(split_from(0, 6, 6), [vec![5, 4, 3, 2, 1, 0], vec![]]);
// test out-of-bounds and also generics
assert_eq!(
split_from::<i16>(-1i16, 4i16, 10i16),
Vec::<Vec<i16>>::new()
[const { Vec::new() }; 2]
);
}
@ -83,7 +80,7 @@ mod test {
fn diag_test() {
assert_eq!(
diag(2, 3, 0, 0, 7, 7),
vec![
[
vec![(1, 2), (0, 1)],
vec![(3, 4), (4, 5), (5, 6), (6, 7)],
vec![(3, 2), (4, 1), (5, 0)],