performance: use vectorcall for call0 and call_method0

This commit is contained in:
David Hewitt 2020-11-19 14:34:44 +00:00
parent d1248d5743
commit 87bacf1be5
4 changed files with 85 additions and 8 deletions

View File

@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Add context.h functions (`PyContext_New`, etc) to FFI. [#1259](https://github.com/PyO3/pyo3/pull/1259)
- Add `PyAny::is_instance()` method. [#1276](https://github.com/PyO3/pyo3/pull/1276)
- Add support for conversion between `char` and `PyString`. [#1282](https://github.com/PyO3/pyo3/pull/1282)
- Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1285](https://github.com/PyO3/pyo3/pull/1285)
- Add FFI definitions for `PyBuffer_SizeFromFormat`, `PyObject_LengthHint`, `PyObject_CallNoArgs`, `PyObject_CallOneArg`, `PyObject_CallMethodNoArgs`, `PyObject_CallMethodOneArg`, `PyObject_VectorcallDict`, and `PyObject_VectorcallMethod`. [#1287](https://github.com/PyO3/pyo3/pull/1287)
### Changed
- Change return type `PyType::name()` from `Cow<str>` to `PyResult<&str>`. [#1152](https://github.com/PyO3/pyo3/pull/1152)
@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Change `Debug` and `Display` impls for `PyException` to be consistent with `PyAny`. [#1275](https://github.com/PyO3/pyo3/pull/1275)
- Change `Debug` impl of `PyErr` to output more helpful information (acquiring the GIL if necessary). [#1275](https://github.com/PyO3/pyo3/pull/1275)
- Rename `PyTypeInfo::is_instance` and `PyTypeInfo::is_exact_instance` to `PyTypeInfo::is_type_of` and `PyTypeInfo::is_exact_type_of`. [#1278](https://github.com/PyO3/pyo3/pull/1278)
- Optimize `PyAny::call0`, `Py::call0` and `PyAny::call_method0` and `Py::call_method0` on Python 3.9 and up. [#1287](https://github.com/PyO3/pyo3/pull/1285)
- Deprecate `Python::is_instance`, `Python::is_subclass`, `Python::release`, and `Python::xdecref`. [#1292](https://github.com/PyO3/pyo3/pull/1292)
### Removed
@ -39,8 +40,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
### Fixed
- Fix missing field in `PyCodeObject` struct (`co_posonlyargcount`) - caused invalid access to other fields in Python >3.7. [#1260](https://github.com/PyO3/pyo3/pull/1260)
- Fix building for `x86_64-unknown-linux-musl` target from `x86_65-unknown-linux-gnu` host. [#1267](https://github.com/PyO3/pyo3/pull/1267)
- Fix FFI definitions for `PyObject_Vectorcall` and `PyVectorcall_Call`. [#1285](https://github.com/PyO3/pyo3/pull/1285)
- Fix `#[text_signature]` interacting badly with rust `r#raw_identifiers`. [#1286](https://github.com/PyO3/pyo3/pull/1286)
- Fix FFI definitions for `PyObject_Vectorcall` and `PyVectorcall_Call`. [#1287](https://github.com/PyO3/pyo3/pull/1285)
- Fix building with Anaconda python inside a virtualenv. [#1290](https://github.com/PyO3/pyo3/pull/1290)
## [0.12.3] - 2020-10-12

View File

@ -15,6 +15,7 @@ build = "build.rs"
edition = "2018"
[dependencies]
cfg-if = { version = "1.0" }
ctor = { version = "0.1", optional = true }
indoc = { version = "1.0.3", optional = true }
inventory = { version = "0.1.4", optional = true }

View File

@ -272,8 +272,19 @@ impl<T> Py<T> {
///
/// This is equivalent to the Python expression `self()`.
pub fn call0(&self, py: Python) -> PyResult<PyObject> {
cfg_if::cfg_if! {
// TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415.
// Once the issue is resolved, we can enable this optimization for limited API.
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
PyObject::from_owned_ptr_or_err(py, ffi::_PyObject_CallNoArg(self.as_ptr()))
}
} else {
self.call(py, (), None)
}
}
}
/// Calls a method on the object.
///
@ -316,8 +327,18 @@ impl<T> Py<T> {
///
/// This is equivalent to the Python expression `self.name()`.
pub fn call_method0(&self, py: Python, name: &str) -> PyResult<PyObject> {
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
let name = name.into_py(py);
PyObject::from_owned_ptr_or_err(py, ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
}
} else {
self.call_method(py, name, (), None)
}
}
}
/// Create a `Py<T>` instance by taking ownership of the given FFI pointer.
///

View File

@ -226,8 +226,19 @@ impl PyAny {
///
/// This is equivalent to the Python expression `self()`.
pub fn call0(&self) -> PyResult<&PyAny> {
cfg_if::cfg_if! {
// TODO: Use PyObject_CallNoArgs instead after https://bugs.python.org/issue42415.
// Once the issue is resolved, we can enable this optimization for limited API.
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
self.py().from_owned_ptr_or_err(ffi::_PyObject_CallNoArg(self.as_ptr()))
}
} else {
self.call((), None)
}
}
}
/// Calls the object with only positional arguments.
///
@ -283,8 +294,18 @@ impl PyAny {
///
/// This is equivalent to the Python expression `self.name()`.
pub fn call_method0(&self, name: &str) -> PyResult<&PyAny> {
cfg_if::cfg_if! {
if #[cfg(all(Py_3_9, not(Py_LIMITED_API)))] {
// Optimized path on python 3.9+
unsafe {
let name = name.into_py(self.py());
self.py().from_owned_ptr_or_err(ffi::PyObject_CallMethodNoArgs(self.as_ptr(), name.as_ptr()))
}
} else {
self.call_method(name, (), None)
}
}
}
/// Calls a method on the object with only positional arguments.
///
@ -464,8 +485,17 @@ impl PyAny {
#[cfg(test)]
mod test {
use crate::types::{IntoPyDict, PyList, PyLong};
use crate::{Python, ToPyObject};
use crate::{
types::{IntoPyDict, PyList, PyLong, PyModule},
Python, ToPyObject,
};
macro_rules! test_module {
($py:ident, $code:literal) => {
PyModule::from_code($py, indoc::indoc!($code), file!(), "test_module")
.expect("module creation failed")
};
}
#[test]
fn test_call_for_non_existing_method() {
@ -488,6 +518,30 @@ mod test {
assert_eq!(list.extract::<Vec<i32>>(py).unwrap(), vec![7, 6, 5, 4, 3]);
}
#[test]
fn test_call_method0() {
Python::with_gil(|py| {
let module = test_module!(
py,
r#"
class SimpleClass:
def foo(self):
return 42
"#
);
let simple_class = module.getattr("SimpleClass").unwrap().call0().unwrap();
assert_eq!(
simple_class
.call_method0("foo")
.unwrap()
.extract::<u32>()
.unwrap(),
42
);
})
}
#[test]
fn test_type() {
let gil = Python::acquire_gil();