Skip to content

More jit options, specifically nogil=True on the serial kernels #213

Open

Description

@selmanozleyen

Hi @flying-sheep I was wondering why the serial (fallback) kernels aren't compiled with nogil=True. This would enable parallelization via joblib and dask schedulers. Is there a reason for it? If not I can open a PR showing the speedup also.

I also want to point to something I learned. Tbb doesn't oversubscribe because it uses a process-wide arena. So, we can suggest tbb in certain OS'es instead of falling back. But setting nogil=True will be enough to be fast enough in itself.

Activity

  1. flying-sheep commented on Sep 18, 2026

    @flying-sheep
    Member

    No good reason, I just ported all things as they were.

    @ilan-gold and I discussed elsewhere that our njit wrapper should actually just expose all the knobs that make sense: https://numba.readthedocs.io/en/stable/reference/jit-compilation.html#numba.jit

    • nopython – maybe we should just switch to jit and set nopython=True as the default, as compat mode might come in handy
    • nogil – unclear what the default should be, please investigate if there are potential issues (maybe there are only potential issues with e.g. nopython=False or so)
    • error_model – might come in handy for functions where divisions by zero creating INF makes sense
    • inline & force_inline – help to modularize code without sacrificing performance

    maybe:

    no:

    • forceobj – I don’t know why one would want this, but if someone can tell me, maybe it can be bumped up
    • cache & parallel – the whole point of our wrapper is to smartly dispatch both while having caching. If users don’t want this, they should just use stock jit/njit

    PS: I didn’t immediately make a 1.5.1 release with #212. If this issue takes longer to complete, we should release 1.5.1 in the meantime, what do you think?

  2. changed the title [-]Why isn't nogil=True on the serial kernels[/-] [+]More `jit` options, specifically `nogil=True` on the serial kernels[/+] on Sep 18, 2026
  3. selmanozleyen commented on Sep 18, 2026

    @selmanozleyen
    MemberAuthor

    Let me have a deep look into this. Normally nogil=True by default on serial would give quick wins for me in squidpy and plenty of reuse but I will have a look because it seems worth it. This is one prevents me of reusing honestly fau to it's full extend.

    But I think we can go with a release now because I want to dig deep a bit about the side effects and stuff

  4. ilan-gold commented on Sep 18, 2026

    @ilan-gold
    Contributor

    nogil – unclear what the default should be, please investigate if there are potential issues (maybe there are only potential issues with e.g. nopython=False or so)

    The only reason I haven't jumped at this in the past is because nogil means that thread safety is no longer guaranteed i.e., in a dask thread-based scheduler. Granted, you shouldn't be doing "edit" operations on shared buffers in dask in the first place, but that is the only risk. I would probably make a big fat changelog note, run downstream packages' test suites, and if no problems, let it rip!

  5. selmanozleyen commented on Sep 18, 2026

    @selmanozleyen
    MemberAuthor

    I think I will make a simple PR changing the default of nogil. On the other parameters I have some questions about fau in general before even considering implementing them. Besides the fallback of parallel and serial, I don't get why we wrap the whole njit tbh. For example why don't we provide nogil_thread_safety_fallback as a wrapper that only consumes parallel=True and gives the rest to njit as is? If the user gave something incompatible, it would be numbas problem and we would be fulfilling our contract. I will create separate issues about this I think

    The only reason I haven't jumped at this in the past is because nogil means that thread safety is no longer guaranteed i.e., in a dask thread-based schedule

    @ilan-gold I agree that it's a behavior change and I will write a changelog but I wouldn't call it a contractual breaking change since the nogil=False and parallelize=False wouldn't guarantee thread safety as well. It would perhaps accidentally avoid data corruptio but a synchronization unaware algorithm is unsafe technically as long as there is context switching and threads in the OS.

    In fact I made claude write this example, which by setting more frequent context switches would make the GIL locks shorter and would make it stop acting like an accidental mutex:

    import sys
    import threading
    
    import numba
    import numpy as np
    
    sys.setswitchinterval(1e-6)  # force frequent handovers; the default 5 ms hides the race
    
    
    @numba.njit(nogil=False)
    def read(out):
        return out[0]
    
    
    @numba.njit(nogil=False)
    def write(out, value):
        out[0] = value
    
    
    out = np.zeros(1, dtype=np.int64)
    
    
    def bump():
        for _ in range(20_000):
            # the GIL is held *through* each call and released *between* them,
            # so the other thread slips in right here and both write the same value
            write(out, read(out) + 1)
    
    
    ts = [threading.Thread(target=bump) for _ in range(2)]
    for t in ts:
        t.start()
    for t in ts:
        t.join()
    
    print(out[0], "of", 40_000)  # ~27000: a third of the updates lost, at nogil=False
    
    25813 of 40000
    27149 of 40000
    27050 of 40000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions