[Cython] Calling gil-requiring function not allowed without gil

Dag Sverre Seljebotn d.s.seljebotn at astro.uio.no
Thu Aug 18 08:58:41 CEST 2011


On 08/18/2011 08:39 AM, Dag Sverre Seljebotn wrote:
> On 08/17/2011 09:21 PM, Robert Bradshaw wrote:
>> On Wed, Aug 17, 2011 at 11:46 AM, Dag Sverre Seljebotn
>> <d.s.seljebotn at astro.uio.no> wrote:
>>> On 08/17/2011 08:19 PM, Robert Bradshaw wrote:
>>>>
>>>> That's a nice idea. I have to admit that all these special gil
>>>> declarations are a bit messy. I'd also rather introduce clear
>>>> decorators, e.g.
>>>>
>>>> @cython.requires_gil # expects gil
>>>> cdef a(): ...
>>>>
>>>> @cython.requires.gil(False) # nogil
>>>> cdef b(): ...
>>>>
>>>> @cython.aquires_gil # with gil
>>>> cdef c(): ...
>>>>
>>>> (Actually, now that we have the "with gil" statement, it could be
>>>> worth considering simply noticing the pattern of the entire function
>>>> body in a with gil block/as the first statement and acquiring the GIL
>>>> before argument parsing.)
>>>>
>>>> Note that we need to declare functions as requiring the GIL to allow
>>>> for declaring cpython.pxd if extern functions are implicitly nogil.
>>>
>>> I agree, it's messy in the current situation, simplifying would be good.
>>>
>>> Assuming we can't acquire the GIL in every single function just to be
>>> sure,
>>> I have a hunch that the "acquires_gil" aspect of a function is just
>>> declared
>>> in the wrong place. I mean, the same function might be passed as a
>>> callback
>>> to C both while holding the GIL and while not holding the GIL -- it
>>> would be
>>> nice to automatically wrap it in a GIL-acquiring wrapper only when
>>> needed.
>>>
>>> So to me it makes more sense to have acquires_gil be part of function
>>> pointer types, or of the C call where the pointer is passed, or similar.
>>> Sort of like an FFI. Can't think of a practical scheme that's more
>>> user-friendly than the current way though...
>>
>> I was thinking the opposite, "aquires_gil" should be completely
>> transparent to the caller (assuming it's cheap enough to
>> check-or-acquire, but that's an optimization not API issue). On the
>> other hand requires/does not require does need to be visible to the
>> caller though, which argues for it being part of the signature.
>
> Are you saying that every single function cdef function, ever, that are
> not "nogil" should have acqusition semantics?
>
> Those semantics would be great, but I worry a bit about performance --
> we just agreed to make function GIL-acquisition even a bit more
> expensive with that check...
>
> Hmm. How about this:
>
> i) Every cdef function that needs the GIL to be held generates a
> GIL-acquiring wrapper function as well.
>
> ii) Whenever taking the address of such a function, you get the address
> of the GIL-requiring wrapper, which can safely be used from any code,
> whether holding the GIL or not.
>
> iii) However, Cython code that already holds the GIL can call the inner
> function directly, as an optimization. Should be possible to do this
> across pxd's as well.
>
> iv) We may introduce a cython.nogil_function_address or similar just to
> provide an option to get around ii).
>
> v) The "with gil" on functions simply ceases to take effect, and is
> deprecated from the language.

Relevant benchmarks on my laptop:

Calling an almost-noop cdef function (in a loop): 0.7 ns per call

Calling same function with "with gil": 58 ns per call

A dict lookup + Python integer-object addition: 57 ns per iteration

I think the cost (basically 2 python ops) is too high to go for the 
simpler scheme of simply making everything "with gil" that is not 
"nogil"? Also, the GIL-initialization check will make it even more 
expensive.

cdef int noop(int i) nogil:
     return 2 * i

cdef int withgil(int i) with gil:
     return 2 * i


def call_noop(int reps):
     cdef int i
     cdef int s = 0
     for i in range(reps):
         s += noop(i)
     return s

def call_withgil(int reps):
     cdef int i
     cdef int s = 0
     for i in range(reps):
         s += withgil(i)
     return s

def call_dict(int reps):
     cdef int i
     cdef object s = 0
     for i in range(reps):
         s += d['2']
     return s

d = {}

for i in range(15):
     d[str(i)] = i

Dag SVerre


More information about the cython-devel mailing list