Add intern! macro which can be used to amortize the cost of creating Python objects by storing them inside a GILOnceCell.
This commit is contained in:
parent
d3dcbd72ba
commit
db109867d3
|
@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Added methods on `InterpreterConfig` to run Python scripts using the configured executable. [#2092](https://github.com/PyO3/pyo3/pull/2092)
|
||||
- Added FFI definitions for `PyType_FromModuleAndSpec`, `PyType_GetModule`, `PyType_GetModuleState` and `PyModule_AddType`. [#2250](https://github.com/PyO3/pyo3/pull/2250)
|
||||
- Add `PyString::intern` to enable usage of the Python's built-in string interning. [#2268](https://github.com/PyO3/pyo3/pull/2268)
|
||||
- Add `intern!` macro which can be used to amortize the cost of creating Python objects by storing them inside a `GILOnceCell`. [#2269](https://github.com/PyO3/pyo3/pull/2269)
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -142,6 +142,10 @@ harness = false
|
|||
name = "bench_tuple"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "bench_intern"
|
||||
harness = false
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
"pyo3-ffi",
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
use criterion::{criterion_group, criterion_main, Bencher, Criterion};
|
||||
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use pyo3::{intern, prepare_freethreaded_python};
|
||||
|
||||
fn getattr_direct(b: &mut Bencher<'_>) {
|
||||
prepare_freethreaded_python();
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let sys = py.import("sys").unwrap();
|
||||
|
||||
b.iter(|| sys.getattr("version").unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
fn getattr_intern(b: &mut Bencher<'_>) {
|
||||
prepare_freethreaded_python();
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let sys = py.import("sys").unwrap();
|
||||
|
||||
b.iter(|| sys.getattr(intern!(py, "version")).unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("getattr_direct", getattr_direct);
|
||||
c.bench_function("getattr_intern", getattr_intern);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
|
@ -375,6 +375,7 @@ pub mod impl_;
|
|||
mod instance;
|
||||
pub mod marker;
|
||||
pub mod marshal;
|
||||
#[macro_use]
|
||||
pub mod once_cell;
|
||||
pub mod panic;
|
||||
pub mod prelude;
|
||||
|
|
|
@ -101,3 +101,46 @@ impl<T> GILOnceCell<T> {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts `value` into a Python object and stores it in static storage. The same Python object
|
||||
/// is returned on each invocation.
|
||||
///
|
||||
/// Because it is stored in a static, this object's destructor will not run.
|
||||
///
|
||||
/// # Example: Using `intern!` to avoid needlessly recreating the same object
|
||||
///
|
||||
/// ```
|
||||
/// use pyo3::intern;
|
||||
/// # use pyo3::{pyfunction, types::PyDict, PyResult, Python};
|
||||
///
|
||||
/// #[pyfunction]
|
||||
/// fn create_dict(py: Python<'_>) -> PyResult<&PyDict> {
|
||||
/// let dict = PyDict::new(py);
|
||||
/// // 👇 A new `PyString` is created
|
||||
/// // for every call of this function
|
||||
/// dict.set_item("foo", 42)?;
|
||||
/// Ok(dict)
|
||||
/// }
|
||||
///
|
||||
/// #[pyfunction]
|
||||
/// fn create_dict_faster(py: Python<'_>) -> PyResult<&PyDict> {
|
||||
/// let dict = PyDict::new(py);
|
||||
/// // 👇 A `PyString` is created once and reused
|
||||
/// // for the lifetime of the program.
|
||||
/// dict.set_item(intern!(py, "foo"), 42)?;
|
||||
/// Ok(dict)
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! intern {
|
||||
($py: expr, $value: expr) => {{
|
||||
static INTERNED: $crate::once_cell::GILOnceCell<$crate::PyObject> =
|
||||
$crate::once_cell::GILOnceCell::new();
|
||||
|
||||
INTERNED
|
||||
.get_or_init($py, || {
|
||||
$crate::conversion::ToPyObject::to_object($value, $py)
|
||||
})
|
||||
.as_ref($py)
|
||||
}};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue