Efficient ways of computing a centered gradient?

I’m trying different implementations to compute a centered gradient, so (f(x+1) - f(x-1))/2 instead of f(x+1)-f(x). Except I’m not dividing by 2 for performances reasons. To avoid complications at the boundary, I can also get rid of them, so the gradient matrix is of size (nrows-2, ncols-2). I’ve tried two ways for the moment, and it appears that the slice way is way faster than the from_fn one. Are there other more idiomatic and efficient ways of computing this in Rust with nalgebra?

fn gradient_x_inner_from_fn(mat: &DMatrix<u8>) -> DMatrix<i16> {
    let (nb_rows, nb_cols) = mat.shape();
    DMatrix::from_fn(nb_rows - 2, nb_cols - 2, |r, c| {
        mat[(r + 1, c + 2)] as i16 - mat[(r + 1, c)] as i16
    })
}

fn gradient_x_inner_slice(mat: &DMatrix<u8>) -> DMatrix<i16> {
    let (nb_rows, nb_cols) = mat.shape();
    mat.slice((1, 2), (nb_rows - 2, nb_cols - 2)).zip_map(
        &mat.slice((1, 0), (nb_rows - 2, nb_cols - 2)),
        |x_2, x_0| x_2 as i16 - x_0 as i16,
    )
}

Since you need the conversion from u8 to i16, the zip_map option is the best approach I can think of. (Without this conversion you could simply have subtracted the two slices to get the difference of course.)

2 Likes

Cool, that’s what I thought also, but since I’m still exploring and nalgebra is huuge (and awesome!) I don’t want to miss stuff.

PS: I had a friend more familiar to Rust investigate the performance issues. Turns out that from_fn is walking through the memory in a row-wise manner instead of column-wise. And since the memory representation is column major apparently, this has a big impact for big matrices. He did a PR here https://github.com/sebcrozet/nalgebra/pull/355.

1 Like

Thanks, that’s a good observation.