Lazy-initialize the global reference pool to reduce its overhead when unused (#4178)
* Add benchmarks exercising the global reference count decrement pool. * Lazy-initialize the global reference pool to reduce its overhead when unused
This commit is contained in:
parent
11d67b3acc
commit
c644c0b0b8
|
@ -18,6 +18,7 @@ rust-version = "1.63"
|
|||
cfg-if = "1.0"
|
||||
libc = "0.2.62"
|
||||
memoffset = "0.9"
|
||||
once_cell = "1"
|
||||
|
||||
# ffi bindings to the python interpreter, split into a separate crate so they can be used independently
|
||||
pyo3-ffi = { path = "pyo3-ffi", version = "=0.22.0-dev" }
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
The global reference pool (to track pending reference count decrements) is now initialized lazily to avoid the overhead of taking a mutex upon function entry when the functionality is not actually used.
|
|
@ -1,4 +1,12 @@
|
|||
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
use codspeed_criterion_compat::{criterion_group, criterion_main, BatchSize, Bencher, Criterion};
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
mpsc::channel,
|
||||
Arc, Barrier,
|
||||
};
|
||||
use std::thread::spawn;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
|
@ -6,14 +14,108 @@ fn drop_many_objects(b: &mut Bencher<'_>) {
|
|||
Python::with_gil(|py| {
|
||||
b.iter(|| {
|
||||
for _ in 0..1000 {
|
||||
std::mem::drop(py.None());
|
||||
drop(py.None());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn drop_many_objects_without_gil(b: &mut Bencher<'_>) {
|
||||
b.iter_batched(
|
||||
|| {
|
||||
Python::with_gil(|py| {
|
||||
(0..1000)
|
||||
.map(|_| py.None().into_py(py))
|
||||
.collect::<Vec<PyObject>>()
|
||||
})
|
||||
},
|
||||
|objs| {
|
||||
drop(objs);
|
||||
|
||||
Python::with_gil(|_py| ());
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
}
|
||||
|
||||
fn drop_many_objects_multiple_threads(b: &mut Bencher<'_>) {
|
||||
const THREADS: usize = 5;
|
||||
|
||||
let barrier = Arc::new(Barrier::new(1 + THREADS));
|
||||
|
||||
let done = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
let sender = (0..THREADS)
|
||||
.map(|_| {
|
||||
let (sender, receiver) = channel();
|
||||
|
||||
let barrier = barrier.clone();
|
||||
|
||||
let done = done.clone();
|
||||
|
||||
spawn(move || {
|
||||
for objs in receiver {
|
||||
barrier.wait();
|
||||
|
||||
drop(objs);
|
||||
|
||||
done.fetch_add(1, Ordering::AcqRel);
|
||||
}
|
||||
});
|
||||
|
||||
sender
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
b.iter_custom(|iters| {
|
||||
let mut duration = Duration::ZERO;
|
||||
|
||||
let mut last_done = done.load(Ordering::Acquire);
|
||||
|
||||
for _ in 0..iters {
|
||||
for sender in &sender {
|
||||
let objs = Python::with_gil(|py| {
|
||||
(0..1000 / THREADS)
|
||||
.map(|_| py.None().into_py(py))
|
||||
.collect::<Vec<PyObject>>()
|
||||
});
|
||||
|
||||
sender.send(objs).unwrap();
|
||||
}
|
||||
|
||||
barrier.wait();
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
loop {
|
||||
Python::with_gil(|_py| ());
|
||||
|
||||
let done = done.load(Ordering::Acquire);
|
||||
if done - last_done == THREADS {
|
||||
last_done = done;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Python::with_gil(|_py| ());
|
||||
|
||||
duration += start.elapsed();
|
||||
}
|
||||
|
||||
duration
|
||||
});
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("drop_many_objects", drop_many_objects);
|
||||
c.bench_function(
|
||||
"drop_many_objects_without_gil",
|
||||
drop_many_objects_without_gil,
|
||||
);
|
||||
c.bench_function(
|
||||
"drop_many_objects_multiple_threads",
|
||||
drop_many_objects_multiple_threads,
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
|
|
23
src/gil.rs
23
src/gil.rs
|
@ -5,6 +5,8 @@ use crate::impl_::not_send::{NotSend, NOT_SEND};
|
|||
#[cfg(pyo3_disable_reference_pool)]
|
||||
use crate::impl_::panic::PanicTrap;
|
||||
use crate::{ffi, Python};
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::Cell;
|
||||
#[cfg(all(feature = "gil-refs", debug_assertions))]
|
||||
use std::cell::RefCell;
|
||||
|
@ -227,7 +229,9 @@ impl GILGuard {
|
|||
let pool = mem::ManuallyDrop::new(GILPool::new());
|
||||
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
POOL.update_counts(Python::assume_gil_acquired());
|
||||
if let Some(pool) = Lazy::get(&POOL) {
|
||||
pool.update_counts(Python::assume_gil_acquired());
|
||||
}
|
||||
GILGuard::Ensured {
|
||||
gstate,
|
||||
#[cfg(feature = "gil-refs")]
|
||||
|
@ -240,7 +244,9 @@ impl GILGuard {
|
|||
increment_gil_count();
|
||||
let guard = GILGuard::Assumed;
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
POOL.update_counts(guard.python());
|
||||
if let Some(pool) = Lazy::get(&POOL) {
|
||||
pool.update_counts(guard.python());
|
||||
}
|
||||
guard
|
||||
}
|
||||
|
||||
|
@ -307,11 +313,14 @@ impl ReferencePool {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
unsafe impl Send for ReferencePool {}
|
||||
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
unsafe impl Sync for ReferencePool {}
|
||||
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
static POOL: ReferencePool = ReferencePool::new();
|
||||
static POOL: Lazy<ReferencePool> = Lazy::new(ReferencePool::new);
|
||||
|
||||
/// A guard which can be used to temporarily release the GIL and restore on `Drop`.
|
||||
pub(crate) struct SuspendGIL {
|
||||
|
@ -336,7 +345,9 @@ impl Drop for SuspendGIL {
|
|||
|
||||
// Update counts of PyObjects / Py that were cloned or dropped while the GIL was released.
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
POOL.update_counts(Python::assume_gil_acquired());
|
||||
if let Some(pool) = Lazy::get(&POOL) {
|
||||
pool.update_counts(Python::assume_gil_acquired());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -409,7 +420,9 @@ impl GILPool {
|
|||
pub unsafe fn new() -> GILPool {
|
||||
// Update counts of PyObjects / Py that have been cloned or dropped since last acquisition
|
||||
#[cfg(not(pyo3_disable_reference_pool))]
|
||||
POOL.update_counts(Python::assume_gil_acquired());
|
||||
if let Some(pool) = Lazy::get(&POOL) {
|
||||
pool.update_counts(Python::assume_gil_acquired());
|
||||
}
|
||||
GILPool {
|
||||
start: OWNED_OBJECTS
|
||||
.try_with(|owned_objects| {
|
||||
|
|
Loading…
Reference in New Issue