rockamic
rockamic

Reputation: 181

Cython prange with function reading from numpy array (memoryview) freezes, why?

I have to use the rows of a numpy array sequentially so I am doing that with a loop. I tried adding prange for speed but it ends up freezing. I am most likely doing something wrong, I may be misunderstanding the meaning of a race condition in this context. What can be done to fix the problem or to treat the array rows in parallel fashion correctly? Reproducible code below for IPython cells:

%load_ext cython
import numpy as np
%%cython --compile-args=-fopenmp --link-args=-fopenmp --force
# if on Windows, replace line above with: %%cython --compile-args=/openmp --link-args=/openmp --force

cimport cython
from cython.parallel cimport prange

@cython.boundscheck(False)
@cython.wraparound(False)
cdef double sum_row(double [:] arr) nogil:
    cdef int i
    cdef int size = arr.shape[0]
    cdef double s = 0

    for i in range(size):
        s += arr[i]

    return s

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef double sum_all_rows(double [:,:] arr) nogil:
    cdef int i
    cdef int n_rows = arr.shape[1] + 1
    cdef double s = 0

    for i in range(n_rows):
        s += sum_row(arr[i])

    return s

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef double sum_all_rows_parallel(double [:,:] arr) nogil:
    cdef int i
    cdef int n_rows = arr.shape[1] + 1
    cdef double s = 0

    for i in prange(n_rows):
        s += sum_row(arr[i])

    return s
X = np.array([[3.14, 2.71, 0.002],
              [0.5, 1, 4.21],
              [0.001, 0.002, 0.003],
              [-0.1, -0.11, -0.12]])
%timeit sum_all_rows(X)

754 ns ± 38.3 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

sum_all_rows_parallel(X)

And it hangs... I stopped it after roughly 5 minutes. I did not observe any increasing memory usage nor CPU usage.

I have used the toy examples of prange before seeing the expected speedups, so I am able to compile properly.

Also, any suggested readings to better understand this kind of issue?

Thank you for your help.

Upvotes: 0

Views: 531

Answers (1)

DavidW
DavidW

Reputation: 30916

This is because nogil means two slightly different things in different contexts. In

cpdef double sum_all_rows_parallel(double [:,:] arr) nogil:

it means "this function does not require the GIL and can be called without it", while in:

with nogil:
# or
prange(..., nogil=True)

it means "release the GIL".

Therefore the GIL is never actually released in your example (since sum_all_rows_parallel is presumably called from Python, which holds the GIL), but prange is working under the assumption that it has been released.

What you should do is change prange(...) to prange(...,nogil=True). You possibly also need to remove the nogil from the sum_all_rows_parallel function definition.


Arguably it's a bug in Cython that it doesn't warn you about the issue, and should be reported. However, I suspect it may not be easily fixed.

Upvotes: 2

Related Questions