In short:
If I repeatedly multiply two u8
values and the result can overflow u8
's value range, is it more efficient (considering both memory usage and speed) to just use u16
values from the start instead or cast the values to u16
s always before the multiplication to prevent overflow?
The whole story:
I'm writing a Rust+Wasm program where I have a grid of cells that I pass into JavaScript as a raw pointer to an array (very much like in the Rust and WebAssembly tutorial). The grid cannot ever be larger than 250 x 250 cells in size, so I have the properties width
and height
for the grid as u8
s.
So, as a simplified example:
pub struct Grid {
width: u8,
height: u8,
cells: Vec<Cell>
}
I then manipulate the cell values according to their neighboring values in the grid step by step. When doing this, I was getting the index of the cell in question in the grid with:
fn get_cell_index(&self, col: u8, row: u8) -> usize {
(row * self.width + col) as usize
}
However, I realized that with large enough grids this results in an overflow because the multiplication is done with u8
values before the as usize
part is reached (i.e. the result of the multiplication is stored in a u8
type already).
There's an easy workaround for this:
fn get_cell_index(&self, col: u8, row: u8) -> usize {
(row as u16 * self.width as u16 + col as u16) as usize
}
But that - in addition to being ugly - raised the question if there's any sense to do this casting of 3 u8
values to u16
for each cell in the grid, repeated several times when the program is running. The most obvious option would be to just change the width
and height
properties to be u16
the whole time so no casting would be needed.
This is probably the wisest choice efficiency-wise considering that using u16
s instead of u8
s for two values will only take up 2 more bytes of memory from the start, and doing the casts will probably have a non-zero performance (speed) penalty. However, I'd still like to know exactly what kind of efficiency costs each approach has. And to be honest, I kind of like how the current u8
type documents the fact that the dimensional values will never be bigger than 250...
And just to state the obvious: Yes, I know that in this example this is of course a micro-optimization that makes absolutely no sense – but I'm eager to know the theoretical implications of different approaches for making sensible choices in bigger projects.