<snip> > I also think that the lock only matters for Multithreaded code not > Multiprocess. I believe the latter pickles and unpickles any Generator > object (and the underying BitGenerator) and so each process has its own > version. Note that when multiprocessing the recommended procedure is to use > spawn() to generate a sequence of BitGenerators and to use a distinct > BitGenerator in each process. If you do this then you are free from the lock.
Thanks. Just to confirm: does using SeedSequence spawn_key arg generate distinct BitGenerators? As in cdef class Wrapper(): def __init__(self, seed): entropy, num = seed py_gen = PCG64(SeedSequence(entropy, spawn_key=(spawn_key,))) self.rng = <bitgen_t *> py_gen.capsule.PyCapsule_GetPointer(capsule, "BitGenerator") # <--- this cdef Wrapper rng_0 = Wrapper(seed=(123, 0)) cdef Wrapper rng_1 = Wrapper(seed=(123, 1)) And then,of these two objects, do they have distinct BitGenerators? Evgeni > > > Kevin > > > > > > From: Evgeni Burovski > Sent: Monday, December 14, 2020 2:10 PM > To: Discussion of Numerical Python > Subject: Re: [Numpy-discussion] locking np.random.Generator in a cython nogil > context? > > > > On Mon, Dec 14, 2020 at 4:46 PM Kevin Sheppard > > <kevin.k.shepp...@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.burovs...@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@python.org > > >> https://mail.python.org/mailman/listinfo/numpy-discussion > > > > > > _______________________________________________ > > > NumPy-Discussion mailing list > > > NumPy-Discussion@python.org > > > https://mail.python.org/mailman/listinfo/numpy-discussion > > _______________________________________________ > > NumPy-Discussion mailing list > > NumPy-Discussion@python.org > > https://mail.python.org/mailman/listinfo/numpy-discussion > > > > _______________________________________________ > NumPy-Discussion mailing list > NumPy-Discussion@python.org > https://mail.python.org/mailman/listinfo/numpy-discussion _______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@python.org https://mail.python.org/mailman/listinfo/numpy-discussion