Nogil with jitclass?

Dear all,
I am trying to use joblib parallel (with threading backend) to parallelize a set of calls to a jitclass. That doesn’t seem to work, probably because jitclass doesn’t release the GIL. Is there a way to pass something like nogil=True to jitclass, or is that generally not supported?

Thanks!

Hi @wielandbrendel,

From memory, @njit flags in general cannot be passed to jitclass, construction is “automatic” and uses njit directly.


Hope that answers your question?

Thanks so much! So the short answer is: it is currently not possible to use jitclass without GIL, right?

1 Like

Yes, this is the case.

I’m sure I’m misunderstanding, but I had thought that a nogil njit function calling another not-declared-nogil njit function would not reacquire the gil- is that correct? Sample pseudocode:

@njit(nogil=False)
def has_gil():
    # is gil owned here?
    pass

@njit(nogil=True)
def no_gil():
    has_gil()

no_gil()

Could this be added without too much work? I envision the following:

from numba.experimental import jitclass
import numba as nb

@jitclass()
class Class1:
    a: float

    def __init__(self, a: float) -> None:
        self.a = a

    @nb.jit(nogil=True, nopython=True, fastmath=True, parallel=True)
    def score(self, thing: float) -> float:
        res = thing
        for _ in nb.prange(10):
            res += self.a
        return res

c = QualDiffScoring(1)
c.score(1)  # works

Which from some preliminary monkey patching can be achieved with:

    jit_methods, methods, props, static_methods, others = {}, {}, {}, {}, {}
    for k, v in clsdct.items():
        if isinstance(v, pytypes.FunctionType):
            methods[k] = v
        elif isinstance(v, property):
            props[k] = v
        elif isinstance(v, staticmethod):
            static_methods[k] = v
        elif isinstance(v, dispatcher.Dispatcher):
            jit_methods[k] = v
        else:
            others[k] = v

    ...

    jit_methods.update({k: njit(v) for k, v in methods.items()})

In numba.experimental.jitclass.base.py.

For my use case, I have many classes with a score method and so it’s helpful to namespace them, as opposed to having:

@jitclass()
class Class1:
    a: float

    def __init__(self, a: float) -> None:
        self.a = a

@nb.jit(nogil=True, nopython=True, fastmath=True, parallel=True)
def score_class1(c: Class1, thing: float) -> float:
    res = thing
    for _ in nb.prange(10):
        res += c.a
    return res

And then having to pass both things around in tandem.