Skip to content

Commit

Permalink
stub: hybrid vec
Browse files Browse the repository at this point in the history
performance is worse than either vec or arrayvec so avoid
  • Loading branch information
dogeystamp committed Jan 18, 2025
1 parent bbb22a6 commit 1055a2a
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 8 deletions.
8 changes: 4 additions & 4 deletions src/movegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use crate::{
};
use std::ops::Not;

/// Most moves possible (to store) in a single position.
const MAX_MOVES: usize = 192;
/// Max moves that can be stored per position.
const MAX_MOVES: usize = 256;

/// Internal type alias to help switch vector types easier
pub type MoveList = ArrayVec<MAX_MOVES, Move>;
Expand Down Expand Up @@ -649,7 +649,7 @@ pub trait GenAttackers {
dest: Square,
single: bool,
filter_color: Option<Color>,
) -> impl IntoIterator<Item = (ColPiece, Move)>;
) -> ArrayVec<MAX_MOVES, (ColPiece, Move)>;
}

impl GenAttackers for Board {
Expand All @@ -658,7 +658,7 @@ impl GenAttackers for Board {
dest: Square,
single: bool,
filter_color: Option<Color>,
) -> impl IntoIterator<Item = (ColPiece, Move)> {
) -> ArrayVec<MAX_MOVES, (ColPiece, Move)> {
let mut ret: ArrayVec<MAX_MOVES, (ColPiece, Move)> = ArrayVec::new();

/// Filter attackers and add them to the return vector.
Expand Down
33 changes: 31 additions & 2 deletions src/util/arrayvec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ impl<const N: usize, T: Sized> ArrayVec<N, T> {
N
}

/// If true, can't push any more elements to this vector.
pub fn is_full(&self) -> bool {
self.len() == N
}

/// Get the number of elements currently in the vector.
pub fn len(&self) -> usize {
self.len
Expand Down Expand Up @@ -96,7 +101,7 @@ impl<const N: usize, T: Sized> ArrayVec<N, T> {
Ok(())
}

/// Pushes one element to the end of the vector.
/// Appends one element to the end of the vector.
///
/// ***Panics*** if the capacity is exceeded, but only through a debug assertion.
pub fn push(&mut self, x: T) {
Expand Down Expand Up @@ -160,9 +165,14 @@ impl<const N: usize, T: Sized> ArrayVec<N, T> {
std::ptr::swap(pa, pb);
}
}

/// Immutable reference iterator over this vector.
pub fn iter(&self) -> ArrayVecIter<'_, N, T> {
ArrayVecIter { arr: self, i: 0 }
}
}

impl<const N: usize, T: Sized + PartialOrd> ArrayVec<N, T> {
impl<const N: usize, T: Sized + Ord> ArrayVec<N, T> {
/// Sort in-place using [selection sort](https://en.m.wikipedia.org/wiki/Selection_sort).
///
/// This algorithm is more effective for small-sized arrays.
Expand Down Expand Up @@ -259,6 +269,25 @@ impl<const N: usize, T: Sized> Iterator for ArrayVecIntoIter<N, T> {
}
}

pub struct ArrayVecIter<'a, const N: usize, T: Sized> {
arr: &'a ArrayVec<N, T>,
i: usize,
}

impl<'a, const N: usize, T> Iterator for ArrayVecIter<'a, N, T> {
type Item = &'a T;

fn next(&mut self) -> Option<Self::Item> {
if (0..self.arr.len).contains(&self.i) {
let ret = Some(&self.arr[self.i]);
self.i += 1;
ret
} else {
None
}
}
}

impl<const N: usize, A: Sized> FromIterator<A> for ArrayVec<N, A> {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
let mut ret = ArrayVec::<N, A>::new();
Expand Down
202 changes: 202 additions & 0 deletions src/util/hybridvec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
This file is part of chess_inator.
chess_inator is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
chess_inator is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with chess_inator. If not, see https://www.gnu.org/licenses/.
Copyright © 2025 dogeystamp <[email protected]>
*/

use crate::util::arrayvec::{ArrayVec, ArrayVecIntoIter};

/// Vector-like structure that stores on either stack or heap.
///
/// The stack is preferred, and when the space is exhausted, the vector is transferred to heap.
pub struct HybridVec<const N: usize, T: Sized> {
data: ArrayVec<N, T>,
heap: Vec<T>,
is_heap: bool,
}

/// Directly pass through a method to the underlying vector.
macro_rules! pass_through {
($(#[$attr:meta])* => $fn_name: ident() -> $ret: ty) => {
$(#[$attr])*
pub fn $fn_name(&self) -> $ret {
if self.is_heap {
self.heap.$fn_name()
} else {
self.data.$fn_name()
}
}
};
($(#[$attr:meta])* => mut $fn_name: ident() -> $ret: ty) => {
$(#[$attr])*
pub fn $fn_name(&mut self) -> $ret {
if self.is_heap {
self.heap.$fn_name()
} else {
self.data.$fn_name()
}
}
}
}

impl<const N: usize, T: Sized + Clone> HybridVec<N, T> {
/// Create a new empty array.
pub fn new() -> Self {
Self {
data: ArrayVec::<N, T>::new(),
heap: Vec::new(),
is_heap: false,
}
}

/// If true, this vector is heap-allocated. Otherwise, it is on the stack.
pub fn is_heap(&self) -> bool {
self.is_heap
}

pub fn push(&mut self, x: T) {
if !self.is_heap() {
if self.data.is_full() {
// move to heap
self.is_heap = true;
self.heap.reserve(self.data.capacity() + 1);
for elem in self.data.iter().cloned() {
self.heap.push(elem);
}
self.heap.push(x);
} else {
self.data.push(x);
}
} else {
self.heap.push(x);
}
}

pass_through!(
/// Get the number of elements this vector can hold without reallocating.
=> capacity() -> usize
);

pass_through!(
/// Get the number of elements currently in the vector.
=> len() -> usize
);

pass_through!(
/// Get the number of elements currently in the vector.
=> is_empty() -> bool
);

pass_through!(
/// Remove the last element of the vector and return it, if there is one.
=> mut pop() -> Option<T>
);

/// Keep only the elements specified by a boolean predicate. Operates in place.
pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&T) -> bool,
{
if self.is_heap() {
self.heap.retain(f)
} else {
self.data.retain(f)
}
}

/// Swap elements at two indices.
///
/// # Panics
///
/// If indices are out of bounds.
pub fn swap(&mut self, a: usize, b: usize) {
if self.is_heap() {
self.heap.swap(a, b)
} else {
self.data.swap(a, b)
}
}
}

impl<const N: usize, T: Sized + Ord> HybridVec<N, T> {
/// Sort the vector in place.
pub fn sort(&mut self) {
if self.is_heap {
self.heap.sort()
} else {
self.data.selection_sort()
}
}
}

/// Iterator type for [`HybridVec`].
pub enum HybridIntoIter<const N: usize, T: Sized> {
Stack(ArrayVecIntoIter<N, T>),
Heap(<std::vec::Vec<T> as std::iter::IntoIterator>::IntoIter),
}

impl<const N: usize, T: Sized> Iterator for HybridIntoIter<N, T> {
type Item = T;

fn next(&mut self) -> Option<Self::Item> {
match self {
HybridIntoIter::Stack(avi) => avi.next(),
HybridIntoIter::Heap(vi) => vi.next(),
}
}
}

impl<const N: usize, T: Sized> IntoIterator for HybridVec<N, T> {
type Item = T;

type IntoIter = HybridIntoIter<N, T>;

fn into_iter(self) -> Self::IntoIter {
if self.is_heap {
HybridIntoIter::Heap(self.heap.into_iter())
} else {
HybridIntoIter::Stack(self.data.into_iter())
}
}
}

impl<const N: usize, T: Sized + Clone> Default for HybridVec<N, T> {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_hybrid_vec() {
let mut hv = HybridVec::<5, usize>::new();

assert!(!hv.is_heap());

for i in 0..5 {
hv.push(i)
}
assert!(!hv.is_heap());
for i in 0..5 {
hv.push(i)
}
assert!(hv.is_heap());

for i in (0..5).rev() {
assert_eq!(hv.pop(), Some(i))
}

let res = hv.into_iter().collect::<Vec<_>>();
assert_eq!(res, (0..5).into_iter().collect::<Vec<_>>())
}
}
1 change: 1 addition & 0 deletions src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ Copyright © 2025 dogeystamp <[email protected]>
pub mod serialization;
pub mod arrayvec;
pub mod hybridvec;
pub mod random;
6 changes: 4 additions & 2 deletions tests/perft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ fn test_perft() {
// https://www.chessprogramming.org/Perft_Results
let test_cases = [
(
"QQqQqQqq/q6Q/Q6q/q6Q/Q6q/q6Q/Q6q/QqQqQqQq w - - 0 1",
vec![1, 412],
// https://chess.stackexchange.com/a/8392
// test case intended to test what happens when moving HybridVec from stack to heap
"R6R/3Q4/1Q4Q1/4Q3/2Q4Q/Q4Q2/pp1Q4/kBNN1KB1 w - - 0 1",
vec![1, 218],
1,
),
(
Expand Down

0 comments on commit 1055a2a

Please sign in to comment.