mirror of
https://github.com/bspeice/speice.io
synced 2024-11-13 21:48:12 -05:00
Notes about double-unlock
This commit is contained in:
parent
1e18b201f5
commit
7489733f64
@ -3,7 +3,7 @@ layout: post
|
|||||||
title: "Release the GIL: Part 2 - Pybind11, PyO3"
|
title: "Release the GIL: Part 2 - Pybind11, PyO3"
|
||||||
description: "More Python Parallelism"
|
description: "More Python Parallelism"
|
||||||
category:
|
category:
|
||||||
tags: [python]
|
tags: [python, rust, c++]
|
||||||
---
|
---
|
||||||
|
|
||||||
I've been continuing experiments with parallelism in Python; while these techniques are a bit niche,
|
I've been continuing experiments with parallelism in Python; while these techniques are a bit niche,
|
||||||
@ -26,6 +26,9 @@ and Python", and they certainly deliver on that. Setting up a hybrid project whe
|
|||||||
and Python (using setuptools) could coexist was straight-forward, and the repository also works as
|
and Python (using setuptools) could coexist was straight-forward, and the repository also works as
|
||||||
[a template](https://github.com/speice-io/release-the-gil-pybind11/settings) for future projects.
|
[a template](https://github.com/speice-io/release-the-gil-pybind11/settings) for future projects.
|
||||||
|
|
||||||
|
TODO: Include anything about how Pybind11 and Cython are similar because of compilation to C++?
|
||||||
|
Maybe also talk about project setup being a good deal more complicated?
|
||||||
|
|
||||||
Just like the previous post, we'll examine a simple Fibonacci sequence implementation to demonstrate
|
Just like the previous post, we'll examine a simple Fibonacci sequence implementation to demonstrate
|
||||||
how Python's threading model interacts with Pybind11:
|
how Python's threading model interacts with Pybind11:
|
||||||
|
|
||||||
@ -68,10 +71,28 @@ std::uint64_t fibonacci_nogil(std::uint64_t n) {
|
|||||||
py::gil_scoped_release release;
|
py::gil_scoped_release release;
|
||||||
return fibonacci(n);
|
return fibonacci(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PYBIND11_MODULE(speiceio_pybind11, m) {
|
||||||
|
|
||||||
|
m.def("fibonacci_gil", &fibonacci_gil, R"pbdoc(
|
||||||
|
Calculate the Nth Fibonacci number while implicitly holding the GIL
|
||||||
|
)pbdoc");
|
||||||
|
|
||||||
|
m.def("fibonacci_nogil", &fibonacci_nogil,
|
||||||
|
R"pbdoc(
|
||||||
|
Calculate the Nth Fibonacci number after explicitly unlocking the GIL
|
||||||
|
)pbdoc");
|
||||||
|
|
||||||
|
#ifdef VERSION_INFO
|
||||||
|
m.attr("__version__") = VERSION_INFO;
|
||||||
|
#else
|
||||||
|
m.attr("__version__") = "dev";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Admittedly, the project setup is significantly more involved than Cython or Numba. I've omitted
|
After the code is installed into a `virtualenv` or similar setup, we can use the functions to
|
||||||
those steps here, but the full project is available at [INSERT LINK HERE].
|
demonstrate GIL unlocking:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# The billionth Fibonacci number overflows `std::uint64_t`, but that's OK;
|
# The billionth Fibonacci number overflows `std::uint64_t`, but that's OK;
|
||||||
@ -161,3 +182,120 @@ t1.join(); t2.join()
|
|||||||
|
|
||||||
Finally, it's import to note that scheduling matters; in this example, threads run in serial because
|
Finally, it's import to note that scheduling matters; in this example, threads run in serial because
|
||||||
the GIL-locked thread is started first.
|
the GIL-locked thread is started first.
|
||||||
|
|
||||||
|
TODO: Note about double-unlocking:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
void recurse_unlock() {
|
||||||
|
py::gil_scoped_release release;
|
||||||
|
return recurse_unlock();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> Python 3.8.2 (default, Apr 27 2020, 15:53:34)
|
||||||
|
> [GCC 9.3.0] on linux
|
||||||
|
> Type "help", "copyright", "credits" or "license" for more information.
|
||||||
|
> >>> from speiceio_pybind11 import recurse_unlock
|
||||||
|
> >>> recurse_unlock()
|
||||||
|
> Fatal Python error: PyEval_SaveThread: NULL tstate
|
||||||
|
> Python runtime state: initialized
|
||||||
|
>
|
||||||
|
> Current thread 0x00007f213a627740 (most recent call first):
|
||||||
|
> File "<stdin>", line 1 in <module>
|
||||||
|
> [1] 34943 abort (core dumped) python
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
# PyO3
|
||||||
|
|
||||||
|
```python
|
||||||
|
N = 1_000_000_000;
|
||||||
|
|
||||||
|
from speiceio_pyo3 import fibonacci_gil, fibonacci_nogil
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
%%time
|
||||||
|
_ = fibonacci_gil(N)
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> CPU times: user 283 ms, sys: 0 ns, total: 283 ms
|
||||||
|
> Wall time: 282 ms
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
```python
|
||||||
|
%%time
|
||||||
|
_ = fibonacci_nogil(N)
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> CPU times: user 284 ms, sys: 0 ns, total: 284 ms
|
||||||
|
> Wall time: 284 ms
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
```python
|
||||||
|
%%time
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
# Create the two threads to run on
|
||||||
|
t1 = Thread(target=fibonacci_gil, args=[N])
|
||||||
|
t2 = Thread(target=fibonacci_gil, args=[N])
|
||||||
|
# Start the threads
|
||||||
|
t1.start(); t2.start()
|
||||||
|
# Wait for the threads to finish
|
||||||
|
t1.join(); t2.join()
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> CPU times: user 503 ms, sys: 3.83 ms, total: 507 ms
|
||||||
|
> Wall time: 506 ms
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
```python
|
||||||
|
%%time
|
||||||
|
|
||||||
|
t1 = Thread(target=fibonacci_nogil, args=[N])
|
||||||
|
t2 = Thread(target=fibonacci_gil, args=[N])
|
||||||
|
t1.start(); t2.start()
|
||||||
|
t1.join(); t2.join()
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> CPU times: user 501 ms, sys: 3.96 ms, total: 505 ms
|
||||||
|
> Wall time: 252 ms
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
```python
|
||||||
|
%%time
|
||||||
|
|
||||||
|
# Note that the GIL-locked version is started first
|
||||||
|
t1 = Thread(target=fibonacci_gil, args=[N])
|
||||||
|
t2 = Thread(target=fibonacci_nogil, args=[N])
|
||||||
|
t1.start(); t2.start()
|
||||||
|
t1.join(); t2.join()
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> CPU times: user 533 ms, sys: 3.69 ms, total: 537 ms
|
||||||
|
> Wall time: 537 ms
|
||||||
|
> </pre>
|
||||||
|
|
||||||
|
Interestingly enough, Rust's borrow rules actually _prevent_ double-unlocking because the GIL handle
|
||||||
|
can't be transferred across threads:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn recursive_unlock(py: Python) -> PyResult<()> {
|
||||||
|
py.allow_threads(|| recursive_unlock(py))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> <pre>
|
||||||
|
> error[E0277]: `std::rc::Rc<()>` cannot be shared between threads safely
|
||||||
|
> --> src/lib.rs:38:8
|
||||||
|
> |
|
||||||
|
> 38 | py.allow_threads(|| recursive_unlock(py))
|
||||||
|
> | ^^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be shared between threads safely
|
||||||
|
> |
|
||||||
|
> = help: within `pyo3::python::Python<'_>`, the trait `std::marker::Sync` is not implemented for `std::rc::Rc<()>`
|
||||||
|
> </pre>
|
||||||
|
Loading…
Reference in New Issue
Block a user