Skip to content

Commit a8179a9

Browse files
Add vectorcall benchmarks
1 parent 06401a6 commit a8179a9

File tree

4 files changed

+177
-19
lines changed

4 files changed

+177
-19
lines changed

pyo3-benches/benches/bench_call.rs

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ use std::hint::black_box;
22

33
use codspeed_criterion_compat::{criterion_group, criterion_main, Bencher, Criterion};
44

5-
use pyo3::prelude::*;
65
use pyo3::ffi::c_str;
6+
use pyo3::prelude::*;
7+
use pyo3::types::IntoPyDict;
78

89
macro_rules! test_module {
910
($py:ident, $code:literal) => {
@@ -26,6 +27,62 @@ fn bench_call_0(b: &mut Bencher<'_>) {
2627
})
2728
}
2829

30+
fn bench_call_1(b: &mut Bencher<'_>) {
31+
Python::with_gil(|py| {
32+
let module = test_module!(py, "def foo(a, b, c): pass");
33+
34+
let foo_module = &module.getattr("foo").unwrap();
35+
let args = (
36+
<_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py),
37+
<_ as IntoPy<PyObject>>::into_py("s", py).into_bound(py),
38+
<_ as IntoPy<PyObject>>::into_py(1.23, py).into_bound(py),
39+
);
40+
41+
b.iter(|| {
42+
for _ in 0..1000 {
43+
black_box(foo_module).call1(args.clone()).unwrap();
44+
}
45+
});
46+
})
47+
}
48+
49+
fn bench_call(b: &mut Bencher<'_>) {
50+
Python::with_gil(|py| {
51+
let module = test_module!(py, "def foo(a, b, c, d, e): pass");
52+
53+
let foo_module = &module.getattr("foo").unwrap();
54+
let args = (
55+
<_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py),
56+
<_ as IntoPy<PyObject>>::into_py("s", py).into_bound(py),
57+
<_ as IntoPy<PyObject>>::into_py(1.23, py).into_bound(py),
58+
);
59+
let kwargs = [("d", 1), ("e", 42)].into_py_dict(py);
60+
61+
b.iter(|| {
62+
for _ in 0..1000 {
63+
black_box(foo_module)
64+
.call(args.clone(), Some(&kwargs))
65+
.unwrap();
66+
}
67+
});
68+
})
69+
}
70+
71+
fn bench_call_one_arg(b: &mut Bencher<'_>) {
72+
Python::with_gil(|py| {
73+
let module = test_module!(py, "def foo(a): pass");
74+
75+
let foo_module = &module.getattr("foo").unwrap();
76+
let arg = <_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py);
77+
78+
b.iter(|| {
79+
for _ in 0..1000 {
80+
black_box(foo_module).call1((arg.clone(),)).unwrap();
81+
}
82+
});
83+
})
84+
}
85+
2986
fn bench_call_method_0(b: &mut Bencher<'_>) {
3087
Python::with_gil(|py| {
3188
let module = test_module!(
@@ -47,9 +104,96 @@ class Foo:
47104
})
48105
}
49106

107+
fn bench_call_method_1(b: &mut Bencher<'_>) {
108+
Python::with_gil(|py| {
109+
let module = test_module!(
110+
py,
111+
"
112+
class Foo:
113+
def foo(self, a, b, c):
114+
pass
115+
"
116+
);
117+
118+
let foo_module = &module.getattr("Foo").unwrap().call0().unwrap();
119+
let args = (
120+
<_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py),
121+
<_ as IntoPy<PyObject>>::into_py("s", py).into_bound(py),
122+
<_ as IntoPy<PyObject>>::into_py(1.23, py).into_bound(py),
123+
);
124+
125+
b.iter(|| {
126+
for _ in 0..1000 {
127+
black_box(foo_module)
128+
.call_method1("foo", args.clone())
129+
.unwrap();
130+
}
131+
});
132+
})
133+
}
134+
135+
fn bench_call_method(b: &mut Bencher<'_>) {
136+
Python::with_gil(|py| {
137+
let module = test_module!(
138+
py,
139+
"
140+
class Foo:
141+
def foo(self, a, b, c, d, e):
142+
pass
143+
"
144+
);
145+
146+
let foo_module = &module.getattr("Foo").unwrap().call0().unwrap();
147+
let args = (
148+
<_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py),
149+
<_ as IntoPy<PyObject>>::into_py("s", py).into_bound(py),
150+
<_ as IntoPy<PyObject>>::into_py(1.23, py).into_bound(py),
151+
);
152+
let kwargs = [("d", 1), ("e", 42)].into_py_dict(py);
153+
154+
b.iter(|| {
155+
for _ in 0..1000 {
156+
black_box(foo_module)
157+
.call_method("foo", args.clone(), Some(&kwargs))
158+
.unwrap();
159+
}
160+
});
161+
})
162+
}
163+
164+
fn bench_call_method_one_arg(b: &mut Bencher<'_>) {
165+
Python::with_gil(|py| {
166+
let module = test_module!(
167+
py,
168+
"
169+
class Foo:
170+
def foo(self, a):
171+
pass
172+
"
173+
);
174+
175+
let foo_module = &module.getattr("Foo").unwrap().call0().unwrap();
176+
let arg = <_ as IntoPy<PyObject>>::into_py(1, py).into_bound(py);
177+
178+
b.iter(|| {
179+
for _ in 0..1000 {
180+
black_box(foo_module)
181+
.call_method1("foo", (arg.clone(),))
182+
.unwrap();
183+
}
184+
});
185+
})
186+
}
187+
50188
fn criterion_benchmark(c: &mut Criterion) {
51189
c.bench_function("call_0", bench_call_0);
190+
c.bench_function("call_1", bench_call_1);
191+
c.bench_function("call", bench_call);
192+
c.bench_function("call_one_arg", bench_call_one_arg);
52193
c.bench_function("call_method_0", bench_call_method_0);
194+
c.bench_function("call_method_1", bench_call_method_1);
195+
c.bench_function("call_method", bench_call_method);
196+
c.bench_function("call_method_one_arg", bench_call_method_one_arg);
53197
}
54198

55199
criterion_group!(benches, criterion_benchmark);

src/conversion.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ pub trait IntoPy<T>: Sized {
184184
self,
185185
py: Python<'py>,
186186
function: Borrowed<'_, 'py, PyAny>,
187+
_: private::Token,
187188
) -> PyResult<Bound<'py, PyAny>>
188189
where
189190
Self: IntoPy<Py<PyTuple>>,
@@ -213,6 +214,7 @@ pub trait IntoPy<T>: Sized {
213214
py: Python<'py>,
214215
function: Borrowed<'_, 'py, PyAny>,
215216
kwargs: Option<Borrowed<'_, '_, PyDict>>,
217+
_: private::Token,
216218
) -> PyResult<Bound<'py, PyAny>>
217219
where
218220
Self: IntoPy<Py<PyTuple>>,
@@ -248,6 +250,7 @@ pub trait IntoPy<T>: Sized {
248250
_py: Python<'py>,
249251
object: Borrowed<'_, 'py, PyAny>,
250252
method_name: Bound<'py, PyString>,
253+
_: private::Token,
251254
) -> PyResult<Bound<'py, PyAny>>
252255
where
253256
Self: IntoPy<Py<PyTuple>>,
@@ -593,6 +596,7 @@ impl IntoPy<Py<PyTuple>> for () {
593596
self,
594597
py: Python<'py>,
595598
function: Borrowed<'_, 'py, PyAny>,
599+
_: private::Token,
596600
) -> PyResult<Bound<'py, PyAny>> {
597601
unsafe {
598602
cfg_if::cfg_if! {
@@ -616,6 +620,7 @@ impl IntoPy<Py<PyTuple>> for () {
616620
py: Python<'py>,
617621
function: Borrowed<'_, 'py, PyAny>,
618622
kwargs: Option<Borrowed<'_, '_, PyDict>>,
623+
_: private::Token,
619624
) -> PyResult<Bound<'py, PyAny>> {
620625
match kwargs {
621626
Some(kwargs) => unsafe {
@@ -626,7 +631,12 @@ impl IntoPy<Py<PyTuple>> for () {
626631
)
627632
.assume_owned_or_err(py)
628633
},
629-
None => <Self as IntoPy<Py<PyTuple>>>::__py_call_vectorcall1(self, py, function),
634+
None => <Self as IntoPy<Py<PyTuple>>>::__py_call_vectorcall1(
635+
self,
636+
py,
637+
function,
638+
private::Token,
639+
),
630640
}
631641
}
632642

@@ -637,6 +647,7 @@ impl IntoPy<Py<PyTuple>> for () {
637647
_py: Python<'py>,
638648
object: Borrowed<'_, 'py, PyAny>,
639649
method_name: Bound<'py, PyString>,
650+
_: private::Token,
640651
) -> PyResult<Bound<'py, PyAny>> {
641652
cfg_if::cfg_if! {
642653
if #[cfg(all(Py_3_9, not(any(Py_LIMITED_API, PyPy, GraalPy))))] {

src/types/any.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::class::basic::CompareOp;
2-
use crate::conversion::{AsPyPointer, FromPyObjectBound, IntoPy, ToPyObject};
2+
use crate::conversion::{private, AsPyPointer, FromPyObjectBound, IntoPy, ToPyObject};
33
use crate::err::{DowncastError, DowncastIntoError, PyErr, PyResult};
44
use crate::exceptions::{PyAttributeError, PyTypeError};
55
use crate::ffi_ptr_ext::FfiPtrExt;
@@ -1164,6 +1164,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
11641164
self.py(),
11651165
self.as_borrowed(),
11661166
kwargs.map(Bound::as_borrowed),
1167+
private::Token,
11671168
)
11681169
}
11691170

@@ -1175,7 +1176,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
11751176

11761177
#[inline]
11771178
fn call1(&self, args: impl IntoPy<Py<PyTuple>>) -> PyResult<Bound<'py, PyAny>> {
1178-
args.__py_call_vectorcall1(self.py(), self.as_borrowed())
1179+
args.__py_call_vectorcall1(self.py(), self.as_borrowed(), private::Token)
11791180
}
11801181

11811182
#[inline]
@@ -1217,6 +1218,7 @@ impl<'py> PyAnyMethods<'py> for Bound<'py, PyAny> {
12171218
self.py(),
12181219
self.as_borrowed(),
12191220
name.into_py(self.py()).into_bound(self.py()),
1221+
private::Token,
12201222
)
12211223
}
12221224

src/types/tuple.rs

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::iter::FusedIterator;
22

3+
use crate::conversion::private;
34
use crate::ffi::{self, Py_ssize_t};
45
use crate::ffi_ptr_ext::FfiPtrExt;
56
#[cfg(feature = "experimental-inspect")]
@@ -543,6 +544,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+
543544
self,
544545
py: Python<'py>,
545546
function: Borrowed<'_, 'py, PyAny>,
547+
_: private::Token,
546548
) -> PyResult<Bound<'py, PyAny>> {
547549
cfg_if::cfg_if! {
548550
if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {
@@ -577,24 +579,22 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+
577579
py: Python<'py>,
578580
function: Borrowed<'_, 'py, PyAny>,
579581
kwargs: Option<Borrowed<'_, '_, PyDict>>,
582+
_: private::Token,
580583
) -> PyResult<Bound<'py, PyAny>> {
581584
cfg_if::cfg_if! {
582585
if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {
583-
match kwargs {
584-
Some(kwargs) => unsafe {
585-
// We need this to drop the arguments correctly.
586-
let args_bound = [$(self.$n.into_py(py).into_bound(py),)*];
587-
// Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`.
588-
let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*];
589-
ffi::PyObject_VectorcallDict(
590-
function.as_ptr(),
591-
args.as_mut_ptr().add(1),
592-
$length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET,
593-
kwargs.as_ptr(),
594-
)
595-
.assume_owned_or_err(py)
596-
},
597-
None => <Self as IntoPy<Py<PyTuple>>>::__py_call_vectorcall1(self, py, function),
586+
// We need this to drop the arguments correctly.
587+
let args_bound = [$(self.$n.into_py(py).into_bound(py),)*];
588+
// Prepend one null argument for `PY_VECTORCALL_ARGUMENTS_OFFSET`.
589+
let mut args = [std::ptr::null_mut(), $(args_bound[$n].as_ptr()),*];
590+
unsafe {
591+
ffi::PyObject_VectorcallDict(
592+
function.as_ptr(),
593+
args.as_mut_ptr().add(1),
594+
$length + ffi::PY_VECTORCALL_ARGUMENTS_OFFSET,
595+
kwargs.map_or_else(std::ptr::null_mut, |kwargs| kwargs.as_ptr()),
596+
)
597+
.assume_owned_or_err(py)
598598
}
599599
} else {
600600
function.call(<Self as IntoPy<Py<PyTuple>>>::into_py(self, py).into_bound(py), kwargs.as_deref())
@@ -608,6 +608,7 @@ macro_rules! tuple_conversion ({$length:expr,$(($refN:ident, $n:tt, $T:ident)),+
608608
py: Python<'py>,
609609
object: Borrowed<'_, 'py, PyAny>,
610610
method_name: Bound<'py, PyString>,
611+
_: private::Token,
611612
) -> PyResult<Bound<'py, PyAny>> {
612613
cfg_if::cfg_if! {
613614
if #[cfg(all(Py_3_9, not(any(PyPy, GraalPy, Py_LIMITED_API))))] {

0 commit comments

Comments
 (0)