Skip to content

Commit da618c7

Browse files
authored
optimize Py<T>::drop for the case when attached (#5454)
* optimize `Py<T>::drop` for the case when attached * newsfragment
1 parent 28db16c commit da618c7

3 files changed

Lines changed: 58 additions & 35 deletions

File tree

newsfragments/5454.changed.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimize `Py<T>::drop` for the case when attached to the Python interpreter.

src/instance.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,10 +2020,29 @@ where
20202020
#[cfg(feature = "py-clone")]
20212021
impl<T> Clone for Py<T> {
20222022
#[track_caller]
2023+
#[inline]
20232024
fn clone(&self) -> Self {
2024-
unsafe {
2025-
state::register_incref(self.0);
2025+
#[track_caller]
2026+
#[inline]
2027+
fn try_incref(obj: NonNull<ffi::PyObject>) {
2028+
use crate::internal::state::thread_is_attached;
2029+
2030+
if thread_is_attached() {
2031+
// SAFETY: Py_INCREF is safe to call on a valid Python object if the thread is attached.
2032+
unsafe { ffi::Py_INCREF(obj.as_ptr()) }
2033+
} else {
2034+
incref_failed()
2035+
}
20262036
}
2037+
2038+
#[cold]
2039+
#[track_caller]
2040+
fn incref_failed() -> ! {
2041+
panic!("Cannot clone pointer into Python heap without the thread being attached.");
2042+
}
2043+
2044+
try_incref(self.0);
2045+
20272046
Self(self.0, PhantomData)
20282047
}
20292048
}
@@ -2037,11 +2056,30 @@ impl<T> Clone for Py<T> {
20372056
/// However, if the `pyo3_disable_reference_pool` conditional compilation flag
20382057
/// is enabled, it will abort the process.
20392058
impl<T> Drop for Py<T> {
2040-
#[track_caller]
2059+
#[inline]
20412060
fn drop(&mut self) {
2042-
unsafe {
2043-
state::register_decref(self.0);
2061+
// non generic inlineable inner function to reduce code bloat
2062+
#[inline]
2063+
fn inner(obj: NonNull<ffi::PyObject>) {
2064+
use crate::internal::state::thread_is_attached;
2065+
2066+
if thread_is_attached() {
2067+
// SAFETY: Py_DECREF is safe to call on a valid Python object if the thread is attached.
2068+
unsafe { ffi::Py_DECREF(obj.as_ptr()) }
2069+
} else {
2070+
drop_slow(obj)
2071+
}
20442072
}
2073+
2074+
#[cold]
2075+
fn drop_slow(obj: NonNull<ffi::PyObject>) {
2076+
// SAFETY: handing ownership of the reference to `register_decref`.
2077+
unsafe {
2078+
state::register_decref(obj);
2079+
}
2080+
}
2081+
2082+
inner(self.0)
20452083
}
20462084
}
20472085

src/internal/state.rs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const ATTACH_FORBIDDEN_DURING_TRAVERSE: isize = -1;
3232
/// 2) PyGILState_Check always returns 1 if the sub-interpreter APIs have ever been called,
3333
/// which could lead to incorrect conclusions that the thread is attached.
3434
#[inline(always)]
35-
fn thread_is_attached() -> bool {
35+
pub(crate) fn thread_is_attached() -> bool {
3636
ATTACH_COUNT.try_with(|c| c.get() > 0).unwrap_or(false)
3737
}
3838

@@ -298,44 +298,28 @@ impl Drop for ForbidAttaching {
298298
}
299299
}
300300

301-
/// Increments the reference count of a Python object if the thread is attached. If
302-
/// the thread is not attached, this function will panic.
303-
///
304-
/// # Safety
305-
/// The object must be an owned Python reference.
306-
#[cfg(feature = "py-clone")]
307-
#[track_caller]
308-
pub unsafe fn register_incref(obj: NonNull<ffi::PyObject>) {
309-
if thread_is_attached() {
310-
unsafe { ffi::Py_INCREF(obj.as_ptr()) }
311-
} else {
312-
panic!("Cannot clone pointer into Python heap without the thread being attached.");
313-
}
314-
}
315-
316301
/// Registers a Python object pointer inside the release pool, to have its reference count decreased
317302
/// the next time the thread is attached in pyo3.
318303
///
319304
/// If the thread is attached, the reference count will be decreased immediately instead of being queued
320305
/// for later.
321306
///
322307
/// # Safety
323-
/// The object must be an owned Python reference.
324-
#[track_caller]
308+
/// - The object must be an owned Python reference.
309+
/// - The reference must not be used after calling this function.
310+
#[inline]
325311
pub unsafe fn register_decref(obj: NonNull<ffi::PyObject>) {
326-
if thread_is_attached() {
327-
unsafe { ffi::Py_DECREF(obj.as_ptr()) }
328-
} else {
329-
#[cfg(not(pyo3_disable_reference_pool))]
312+
#[cfg(not(pyo3_disable_reference_pool))]
313+
{
330314
get_pool().register_decref(obj);
331-
#[cfg(all(
332-
pyo3_disable_reference_pool,
333-
not(pyo3_leak_on_drop_without_reference_pool)
334-
))]
335-
{
336-
let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
337-
panic!("Cannot drop pointer into Python heap without the thread being attached.");
338-
}
315+
}
316+
#[cfg(all(
317+
pyo3_disable_reference_pool,
318+
not(pyo3_leak_on_drop_without_reference_pool)
319+
))]
320+
{
321+
let _trap = PanicTrap::new("Aborting the process to avoid panic-from-drop.");
322+
panic!("Cannot drop pointer into Python heap without the thread being attached.");
339323
}
340324
}
341325

0 commit comments

Comments
 (0)