improve performance of successful int extract by ~30%
add newsfragment formatting skip slow path on 3.8+ formatting cfg if,else formatting again dedicated macro, change int_convert_u64_or_i64 too add float tests force index call for PyLong_AsUnsignedLongLong perform PyLong check for 3.8 too perform PyLong check for <3.10
This commit is contained in:
parent
1520b058e8
commit
0e876d94d6
|
@ -0,0 +1 @@
|
||||||
|
Improve performance of `extract::<i64>` (and other integer types) by avoiding call to `__index__()` converting the value to an integer for 3.10+. Gives performance improvement of around 30% for successful extraction.
|
|
@ -44,8 +44,39 @@ macro_rules! int_fits_larger_int {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! extract_int {
|
||||||
|
($obj:ident, $error_val:expr, $pylong_as:expr) => {
|
||||||
|
extract_int!($obj, $error_val, $pylong_as, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
($obj:ident, $error_val:expr, $pylong_as:expr, $force_index_call: literal) => {
|
||||||
|
// In python 3.8+ `PyLong_AsLong` and friends takes care of calling `PyNumber_Index`,
|
||||||
|
// however 3.8 & 3.9 do lossy conversion of floats, hence we only use the
|
||||||
|
// simplest logic for 3.10+ where that was fixed - python/cpython#82180.
|
||||||
|
// `PyLong_AsUnsignedLongLong` does not call `PyNumber_Index`, hence the `force_index_call` argument
|
||||||
|
// See https://github.com/PyO3/pyo3/pull/3742 for detials
|
||||||
|
if cfg!(Py_3_10) && !$force_index_call {
|
||||||
|
err_if_invalid_value($obj.py(), $error_val, unsafe { $pylong_as($obj.as_ptr()) })
|
||||||
|
} else if let Ok(long) = $obj.downcast::<crate::types::PyLong>() {
|
||||||
|
// fast path - checking for subclass of `int` just checks a bit in the type $object
|
||||||
|
err_if_invalid_value($obj.py(), $error_val, unsafe { $pylong_as(long.as_ptr()) })
|
||||||
|
} else {
|
||||||
|
unsafe {
|
||||||
|
let num = ffi::PyNumber_Index($obj.as_ptr());
|
||||||
|
if num.is_null() {
|
||||||
|
Err(PyErr::fetch($obj.py()))
|
||||||
|
} else {
|
||||||
|
let result = err_if_invalid_value($obj.py(), $error_val, $pylong_as(num));
|
||||||
|
ffi::Py_DECREF(num);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! int_convert_u64_or_i64 {
|
macro_rules! int_convert_u64_or_i64 {
|
||||||
($rust_type:ty, $pylong_from_ll_or_ull:expr, $pylong_as_ll_or_ull:expr) => {
|
($rust_type:ty, $pylong_from_ll_or_ull:expr, $pylong_as_ll_or_ull:expr, $force_index_call:literal) => {
|
||||||
impl ToPyObject for $rust_type {
|
impl ToPyObject for $rust_type {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn to_object(&self, py: Python<'_>) -> PyObject {
|
fn to_object(&self, py: Python<'_>) -> PyObject {
|
||||||
|
@ -64,18 +95,8 @@ macro_rules! int_convert_u64_or_i64 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'source> FromPyObject<'source> for $rust_type {
|
impl<'source> FromPyObject<'source> for $rust_type {
|
||||||
fn extract(ob: &'source PyAny) -> PyResult<$rust_type> {
|
fn extract(obj: &'source PyAny) -> PyResult<$rust_type> {
|
||||||
let ptr = ob.as_ptr();
|
extract_int!(obj, !0, $pylong_as_ll_or_ull, $force_index_call)
|
||||||
unsafe {
|
|
||||||
let num = ffi::PyNumber_Index(ptr);
|
|
||||||
if num.is_null() {
|
|
||||||
Err(PyErr::fetch(ob.py()))
|
|
||||||
} else {
|
|
||||||
let result = err_if_invalid_value(ob.py(), !0, $pylong_as_ll_or_ull(num));
|
|
||||||
ffi::Py_DECREF(num);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "experimental-inspect")]
|
#[cfg(feature = "experimental-inspect")]
|
||||||
|
@ -106,17 +127,7 @@ macro_rules! int_fits_c_long {
|
||||||
|
|
||||||
impl<'source> FromPyObject<'source> for $rust_type {
|
impl<'source> FromPyObject<'source> for $rust_type {
|
||||||
fn extract(obj: &'source PyAny) -> PyResult<Self> {
|
fn extract(obj: &'source PyAny) -> PyResult<Self> {
|
||||||
let ptr = obj.as_ptr();
|
let val: c_long = extract_int!(obj, -1, ffi::PyLong_AsLong)?;
|
||||||
let val = unsafe {
|
|
||||||
let num = ffi::PyNumber_Index(ptr);
|
|
||||||
if num.is_null() {
|
|
||||||
Err(PyErr::fetch(obj.py()))
|
|
||||||
} else {
|
|
||||||
let val = err_if_invalid_value(obj.py(), -1, ffi::PyLong_AsLong(num));
|
|
||||||
ffi::Py_DECREF(num);
|
|
||||||
val
|
|
||||||
}
|
|
||||||
}?;
|
|
||||||
<$rust_type>::try_from(val)
|
<$rust_type>::try_from(val)
|
||||||
.map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
|
.map_err(|e| exceptions::PyOverflowError::new_err(e.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -146,7 +157,7 @@ int_fits_c_long!(i64);
|
||||||
|
|
||||||
// manual implementation for i64 on systems with 32-bit long
|
// manual implementation for i64 on systems with 32-bit long
|
||||||
#[cfg(any(target_pointer_width = "32", target_os = "windows"))]
|
#[cfg(any(target_pointer_width = "32", target_os = "windows"))]
|
||||||
int_convert_u64_or_i64!(i64, ffi::PyLong_FromLongLong, ffi::PyLong_AsLongLong);
|
int_convert_u64_or_i64!(i64, ffi::PyLong_FromLongLong, ffi::PyLong_AsLongLong, false);
|
||||||
|
|
||||||
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
|
#[cfg(all(target_pointer_width = "64", not(target_os = "windows")))]
|
||||||
int_fits_c_long!(isize);
|
int_fits_c_long!(isize);
|
||||||
|
@ -159,7 +170,8 @@ int_fits_larger_int!(usize, u64);
|
||||||
int_convert_u64_or_i64!(
|
int_convert_u64_or_i64!(
|
||||||
u64,
|
u64,
|
||||||
ffi::PyLong_FromUnsignedLongLong,
|
ffi::PyLong_FromUnsignedLongLong,
|
||||||
ffi::PyLong_AsUnsignedLongLong
|
ffi::PyLong_AsUnsignedLongLong,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(not(Py_LIMITED_API))]
|
#[cfg(not(Py_LIMITED_API))]
|
||||||
|
@ -738,4 +750,27 @@ mod tests {
|
||||||
test_nonzero_common!(nonzero_usize, NonZeroUsize);
|
test_nonzero_common!(nonzero_usize, NonZeroUsize);
|
||||||
test_nonzero_common!(nonzero_i128, NonZeroI128);
|
test_nonzero_common!(nonzero_i128, NonZeroI128);
|
||||||
test_nonzero_common!(nonzero_u128, NonZeroU128);
|
test_nonzero_common!(nonzero_u128, NonZeroU128);
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i64_bool() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let obj = true.to_object(py);
|
||||||
|
assert_eq!(1, obj.extract::<i64>(py).unwrap());
|
||||||
|
let obj = false.to_object(py);
|
||||||
|
assert_eq!(0, obj.extract::<i64>(py).unwrap());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_i64_f64() {
|
||||||
|
Python::with_gil(|py| {
|
||||||
|
let obj = 12.34f64.to_object(py);
|
||||||
|
let err = obj.extract::<i64>(py).unwrap_err();
|
||||||
|
assert!(err.is_instance_of::<crate::exceptions::PyTypeError>(py));
|
||||||
|
// with no remainder
|
||||||
|
let obj = 12f64.to_object(py);
|
||||||
|
let err = obj.extract::<i64>(py).unwrap_err();
|
||||||
|
assert!(err.is_instance_of::<crate::exceptions::PyTypeError>(py));
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue