[Numpy-discussion] locking np.random.Generator in a cython nogil context?
Evgeni Burovski
evgeny.burovskiy at gmail.com
Mon Dec 14 09:09:10 EST 2020
On Mon, Dec 14, 2020 at 4:46 PM Kevin Sheppard
<kevin.k.sheppard at gmail.com> wrote:
>
> You need to reacquire the gil, then you can get the lock and rerelease the gil.
>
> I think this works (on phone, so untested)
>
> with gil:
> with nogil, lock:
> ...
Thanks Kevin.
This surely works, but feels seriously weird. Is this the only / the
recommended way?
I can of course adjust the buffer size to amortize the time for the
GIL manipulation, but this really starts looking like a code smell.
My original motivation was to have inner simulation loops python-free.
Most likely, the issue is that I'm not using the Generator correctly?
Evgeni
> On Mon, Dec 14, 2020, 13:37 Evgeni Burovski <evgeny.burovskiy at gmail.com> wrote:
>>
>> Hi,
>>
>> What would be the correct way of locking the bit generator of
>> np.random.Generator in cython's nogil context?
>> (This is threading 101, surely, so please forgive my ignorance).
>>
>> The docs for extending np.random.Generator in cython
>> (https://numpy.org/doc/stable/reference/random/extending.html#cython)
>> recommend the following idiom for generating uniform variates, where
>> the GIL is released and a Generator-specific lock is held:
>>
>> x = PCG64()
>> rng = <bitgen_t *> PyCapsule_GetPointer(x.capsule, capsule_name)
>> with nogil, x.lock:
>> rng.next_double(rng.state)
>>
>> What is the correct way of locking it when already in the nogil
>> section (so that x.lock is not accessible)?
>>
>> The use case is a long-running MC process which generates random
>> variates in a tight loop (so the loop is all nogil). In case it
>> matters, I probably won't be using python threads, but may use
>> multiprocessing.
>>
>> Basically,
>>
>> cdef double uniform(self) nogil:
>> if self.idx >= self.buf.shape[0]:
>> self._fill()
>> cdef double value = self.buf[self.idx]
>> self.idx += 1
>> return value
>>
>> cdef void _fill(self) nogil:
>> self.idx = 0
>> # HERE: Lock ?
>> for i in range(self.buf.shape[0]):
>> self.buf[i] = self.rng.next_double(self.rng.state)
>>
>>
>> Thanks,
>> Evgeni
>>
>>
>> P.S. The full cdef class, for completeness:
>>
>> cdef class RndmWrapper():
>> cdef:
>> double[::1] buf
>> Py_ssize_t idx
>> bitgen_t *rng
>> object py_gen # keep the garbage collector away
>>
>> def __init__(self, seed=(1234, 0), buf_size=4096, bitgen_kind=None):
>> if bitgen_kind is None:
>> bitgen_kind = PCG64
>>
>> # cf Numpy-discussion list, K.~Sheppard, R.~Kern, June 29,
>> 2020 and below
>> # https://mail.python.org/pipermail/numpy-discussion/2020-June/080794.html
>> entropy, num = seed
>> seed_seq = SeedSequence(entropy, spawn_key=(num,))
>> py_gen = bitgen_kind(seed_seq)
>>
>> # store the python object to avoid it being garbage collected
>> self.py_gen = py_gen
>>
>> capsule = py_gen.capsule
>> self.rng = <bitgen_t *>PyCapsule_GetPointer(capsule, capsule_name)
>> if not PyCapsule_IsValid(capsule, capsule_name):
>> raise ValueError("Invalid pointer to anon_func_state")
>>
>> self.buf = np.empty(buf_size, dtype='float64')
>> self._fill()
>>
>> @cython.boundscheck(False)
>> @cython.wraparound(False)
>> cdef void _fill(self) nogil:
>> self.idx = 0
>> for i in range(self.buf.shape[0]):
>> self.buf[i] = self.rng.next_double(self.rng.state)
>>
>> @cython.boundscheck(False)
>> @cython.wraparound(False)
>> cdef double uniform(self) nogil:
>> if self.idx >= self.buf.shape[0]:
>> self._fill()
>> cdef double value = self.buf[self.idx]
>> self.idx += 1
>> return value
>> _______________________________________________
>> NumPy-Discussion mailing list
>> NumPy-Discussion at python.org
>> https://mail.python.org/mailman/listinfo/numpy-discussion
>
> _______________________________________________
> NumPy-Discussion mailing list
> NumPy-Discussion at python.org
> https://mail.python.org/mailman/listinfo/numpy-discussion
More information about the NumPy-Discussion
mailing list