[Question] Parallelizing over a list of PyArrays with rayon #363
-
| I have a simple extension which operates on a single  #[pymodule]
fn repro(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
    fn cumsum(array: ArrayView1<'_, f64>) -> Array1<f64> {
        let mut total = 0.;
        Array1::from_iter(
            array
                .as_slice()
                .expect("input not contiguous")
                .iter()
                .map(|v| {
                    total += v;
                    total
                }) 
        )
    }
    #[pyfn(m)]
    #[pyo3(name = "cumsum_single")]
    fn cumsum_single_py<'py>(
        py: Python<'py>,
        array: PyReadonlyArray1<'_, f64>,
    ) -> &'py PyArray1<f64> {
        cumsum(array.as_array()).into_pyarray(py)
    }
    #[pyfn(m)]
    #[pyo3(name = "cumsum_many_sequential")]
    fn cumsum_many_sequential_py<'py>(
        py: Python<'py>,
        arrays: Vec<PyReadonlyArray1<'_, f64>>,
    ) -> Vec<&'py PyArray1<f64>> {
        arrays.into_iter().map(|arr| cumsum_single_py(py, arr)).collect()
    }
    Ok(())
}The problem is when I try to use  The constraints look a bit weird on the rayon side (to my best understanding,      #[pyfn(m)]
    #[pyo3(name = "cumsum_many_rayon")]
    fn cumsum_many_rayon_py<'py>(
        py: Python<'py>,
        arrays: Vec<PyReadonlyArray1<'_, f64>>,
    ) -> Vec<&'py PyArray1<f64>> {
        let arrays: Vec<_> = arrays
            .iter()
            .map(|pa| pa.as_array())
            .collect();
        // first collect: for some reason cannot send PyReadonlyArray<_, _>,
        // with ArrayBase<ViewRepr<_>, _> it works. But they hold references
        // in a way that forces me to materialize a vector, instead of using
        // par_bridge() directly
        let results: Vec<_> = arrays
            .into_par_iter()
            .map(cumsum)
            .collect();
        // second collect: need to turn the parallel iterator back to sequential
        // for into_pyarray
        
        results
            .into_iter()
            .map(|result| result.into_pyarray(py))
            .collect()
        // third collect: to create the actual returned Python list
    }This solution uses three individual  | 
Beta Was this translation helpful? Give feedback.
Replies: 2 comments 1 reply
-
| 
 You are just copying references, not the underlying data. 
 The problem when using Rayon here is that  At the moment I don't see a better way to achieve what you need. I suspect that the overhead of the three calls to  One alternative that would however require a different signature and would preclude the arrays from having different lengths would be to take a  | 
Beta Was this translation helpful? Give feedback.
-
| Thanks a lot for your quick reply, it's good to know I have not strayed too far from what is reasonable :) | 
Beta Was this translation helpful? Give feedback.
You are just copying references, not the underlying data.
The problem when using Rayon here is that
PyReadonlyArray<'py, T, D>: !Sendas it basically wraps a&'py PyArray<T, D>which is notSendeither as it is locked to the lifetime'pyfor which the GIL is held by the current thread.At the moment I don't see a better way to achieve what you need. I suspect that the overhead of the three calls to
collectshould be manageable if what you are doing instead ofcumsumis sufficiently expensive.One alternative that would however require a different signature …