Search code examples
rustcastingchess

In Rust, is it faster to store larger types or to store smaller ones and cast them all the time?


I'm developing a chess engine in Rust. I have a Move struct with from and to fields, which are Square. Square is a struct containing a usize, so that I can use it directly when accessing board elements of the position. Since in Rust indexing must be done with usize, I'm wondering what's the fastest way to handle this situation (note that move generation should be as fast as possible). I understand it's more memory friendly to store u8 and cast them every time I need to use them as an index, but is it faster? What would be the idiomatic way to approach this?

I have:

struct Square {index: usize}

fn position.at(square: Square) -> Option<Piece> {
   position.board[square.index]
}

I've tried migrating to u8 and casting every time with mixed results:

struct Square(u8)

fn position.at(square: Square) -> Option<Piece> {
   position.board[square.0 as usize]
}

Solution

  • Pro u8 casting:

    • better cache utilization (objects are smaller); but might be only interesting when there are a lot of objects

    Con u8:

    • casting might require additional instructions on some platforms; but these are usually only register operations which are optimized by the cpu

    Idiomatic way to avoid the as usize: implement a wrapper

    impl Square {
        #[inline]
        pub fn index(&self) -> usize {
            self.0 as usize
        }
    }
    

    Or, when you want to make it really typesafe, implement std::ops::Index:

    struct Piece;
    struct Board([Piece; 64]);
    
    struct Square(u8);
    
    impl std::ops::Index<Square> for Board {
        type Output = Piece;
    
        fn index(&self, index: Square) -> &Self::Output {
            &self.0[index.0 as usize]
        }
    }