Skip to content

Commit

Permalink
Improve performance on pointer drop
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhewitt committed Apr 8, 2020
1 parent 184bafc commit ca027cf
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 12 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Added
* `FromPyObject` implementations for `HashSet` and `BTreeSet`. [#842](https://github.com/PyO3/pyo3/pull/842)

### Changed

* `PyObject` and `Py<T>` reference counts are now decremented sooner after `drop()`. [#851](https://github.com/PyO3/pyo3/pull/851)
* When the GIL is held, the refcount is now decreased immediately on drop. (Previously would wait until just before releasing the GIL.)
* When the GIL is not held, the refcount is now decreased when the GIL is next acquired. (Previously would wait until next time the GIL was released.)

## [0.9.1]

### Fixed
Expand Down
16 changes: 16 additions & 0 deletions benches/bench_pyobject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#![feature(test)]

extern crate test;
use pyo3::prelude::*;
use test::Bencher;

#[bench]
fn drop_many_objects(b: &mut Bencher) {
let gil = Python::acquire_gil();
let py = gil.python();
b.iter(|| {
for _ in 0..1000 {
std::mem::drop(PyObject::from(py.None()));
}
});
}
1 change: 1 addition & 0 deletions src/ffi/pystate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ extern "C" {
#[cfg_attr(PyPy, link_name = "PyPyGILState_Release")]
pub fn PyGILState_Release(arg1: PyGILState_STATE) -> ();
pub fn PyGILState_GetThisThreadState() -> *mut PyThreadState;
pub fn PyGILState_Check() -> c_int;
}

#[inline]
Expand Down
123 changes: 114 additions & 9 deletions src/gil.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,33 @@
//! Interaction with python's global interpreter lock

use crate::{ffi, internal_tricks::Unsendable, PyAny, Python};
use std::cell::Cell;
use std::ptr::NonNull;
use std::{any, sync};

static START: sync::Once = sync::Once::new();
static START_PYO3: sync::Once = sync::Once::new();

thread_local! {
/// This is a internal counter in pyo3 monitoring whether this thread has the GIL.
///
/// It will be incremented whenever a GIL-holding RAII struct is created, and decremented
/// whenever they are dropped.
///
/// As a result, if this thread has the GIL, GIL_COUNT is greater than zero.
static GIL_COUNT: Cell<u32> = Cell::new(0);
}

/// Check whether the GIL is acquired.
///
/// Note: This uses pyo3's internal count rather than PyGILState_Check for two reasons:
/// 1) for performance
/// 2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called,
/// which could lead to incorrect conclusions that the GIL is held.
pub(crate) fn gil_is_acquired() -> bool {
GIL_COUNT.with(|c| c.get() > 0)
}

/// Prepares the use of Python in a free-threaded context.
///
/// If the Python interpreter is not already initialized, this function
Expand Down Expand Up @@ -110,6 +131,8 @@ impl Drop for GILGuard {
pool.drain(self.python(), self.owned, self.borrowed);
ffi::PyGILState_Release(self.gstate);
}

decrement_gil_count();
}
}

Expand Down Expand Up @@ -177,6 +200,7 @@ pub struct GILPool<'p> {
impl<'p> GILPool<'p> {
#[inline]
pub fn new(py: Python) -> GILPool {
increment_gil_count();
let p: &'static mut ReleasePool = unsafe { &mut *POOL };
GILPool {
py,
Expand All @@ -193,6 +217,7 @@ impl<'p> Drop for GILPool<'p> {
let pool: &'static mut ReleasePool = &mut *POOL;
pool.drain(self.py, self.owned, self.borrowed);
}
decrement_gil_count();
}
}

Expand All @@ -210,7 +235,11 @@ pub unsafe fn register_any<'p, T: 'static>(obj: T) -> &'p T {

pub unsafe fn register_pointer(obj: NonNull<ffi::PyObject>) {
let pool = &mut *POOL;
(**pool.p.lock()).push(obj);
if gil_is_acquired() {
ffi::Py_DECREF(obj.as_ptr())
} else {
(**pool.p.lock()).push(obj);
}
}

pub unsafe fn register_owned(_py: Python, obj: NonNull<ffi::PyObject>) -> &PyAny {
Expand All @@ -233,7 +262,12 @@ impl GILGuard {

unsafe {
let gstate = ffi::PyGILState_Ensure(); // acquire GIL
increment_gil_count();

// Release objects that were dropped since last GIL acquisition
let pool: &'static mut ReleasePool = &mut *POOL;
pool.release_pointers();

GILGuard {
owned: pool.owned.len(),
borrowed: pool.borrowed.len(),
Expand All @@ -250,6 +284,20 @@ impl GILGuard {
}
}

/// Increment pyo3's internal GIL count - to be called whenever GILPool or GILGuard is created.
fn increment_gil_count() {
GIL_COUNT.with(|c| c.set(c.get() + 1))
}

/// Decrement pyo3's internal GIL count - to be called whenever GILPool or GILGuard is dropped.
fn decrement_gil_count() {
GIL_COUNT.with(|c| {
let current = c.get();
assert!(current > 0);
c.set(current - 1);
})
}

use self::array_list::ArrayList;

mod array_list {
Expand Down Expand Up @@ -308,7 +356,7 @@ mod array_list {

#[cfg(test)]
mod test {
use super::{GILPool, NonNull, ReleasePool, POOL};
use super::{GILPool, NonNull, ReleasePool, POOL, GIL_COUNT};
use crate::object::PyObject;
use crate::AsPyPointer;
use crate::Python;
Expand All @@ -327,7 +375,6 @@ mod test {

#[test]
fn test_owned() {
gil::init_once();
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
Expand Down Expand Up @@ -356,7 +403,6 @@ mod test {

#[test]
fn test_owned_nested() {
gil::init_once();
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
Expand Down Expand Up @@ -393,7 +439,6 @@ mod test {
#[test]
fn test_borrowed() {
gil::init_once();

unsafe {
let p: &'static mut ReleasePool = &mut *POOL;

Expand All @@ -420,7 +465,6 @@ mod test {
#[test]
fn test_borrowed_nested() {
gil::init_once();

unsafe {
let p: &'static mut ReleasePool = &mut *POOL;

Expand Down Expand Up @@ -455,8 +499,30 @@ mod test {
}

#[test]
fn test_pyobject_drop() {
gil::init_once();
fn test_pyobject_drop_with_gil_decreases_refcnt() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
// Ensure that obj does not get freed
let _ref = obj.clone_ref(py);
let obj_ptr = obj.as_ptr();

unsafe {
let p: &'static mut ReleasePool = &mut *POOL;

{
assert_eq!(p.owned.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
}

// With the GIL held, obj can be dropped immediately
drop(obj);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}

#[test]
fn test_pyobject_drop_without_gil_doesnt_decrease_refcnt() {
let gil = Python::acquire_gil();
let py = gil.python();
let obj = get_object();
Expand All @@ -471,13 +537,52 @@ mod test {
assert_eq!(p.owned.len(), 0);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);
}

// Without the GIL held, obj cannot be dropped until the next GIL acquire
drop(py);
drop(gil);
drop(obj);
assert_eq!(ffi::Py_REFCNT(obj_ptr), 2);

{
// Next time the GIL is acquired, the object is released
let _gil = Python::acquire_gil();
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
assert_eq!(ffi::Py_REFCNT(obj_ptr), 1);
}
}

#[test]
fn test_gil_counts() {
// Check GILGuard and GILPool both increase counts correctly
let get_gil_count = || GIL_COUNT.with(|c| c.get());

assert_eq!(get_gil_count(), 0);
let gil = Python::acquire_gil();
assert_eq!(get_gil_count(), 1);

let py = gil.python();

assert_eq!(get_gil_count(), 1);
let pool = GILPool::new(py);
assert_eq!(get_gil_count(), 2);

let pool2 = GILPool::new(py);
assert_eq!(get_gil_count(), 3);

drop(pool);
assert_eq!(get_gil_count(), 2);

let gil2 = Python::acquire_gil();
assert_eq!(get_gil_count(), 3);

drop(gil2);
assert_eq!(get_gil_count(), 2);

drop(pool2);
assert_eq!(get_gil_count(), 1);

drop(gil);
assert_eq!(get_gil_count(), 0);
}
}
4 changes: 2 additions & 2 deletions src/internal_tricks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ macro_rules! private_decl {
/// This trait is private to implement; this method exists to make it
/// impossible to implement outside the crate.
fn __private__(&self) -> crate::internal_tricks::PrivateMarker;
}
};
}

macro_rules! private_impl {
Expand All @@ -21,7 +21,7 @@ macro_rules! private_impl {
fn __private__(&self) -> crate::internal_tricks::PrivateMarker {
crate::internal_tricks::PrivateMarker
}
}
};
}

macro_rules! pyo3_exception {
Expand Down
2 changes: 1 addition & 1 deletion src/pycell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ impl<T: PyClass + fmt::Debug> fmt::Debug for PyCell<T> {
/// # let gil = Python::acquire_gil();
/// # let py = gil.python();
/// # let sub = PyCell::new(py, Child::new()).unwrap();
/// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 4)'");
/// # pyo3::py_run!(py, sub, "assert sub.format() == 'Caterpillar(base: Butterfly, cnt: 3)'");
/// ```
pub struct PyRef<'p, T: PyClass> {
inner: &'p PyCellInner<T>,
Expand Down

0 comments on commit ca027cf

Please sign in to comment.