8000 Replace rust-cpython with pyo3 in benches · RustPython/RustPython@ea1f72e · GitHub
[go: up one dir, main page]

Skip to content

Commit ea1f72e

Browse files
committed
Replace rust-cpython with pyo3 in benches
Update the actual benchmark harnesses. Because the internal APIs previously used are no longer available, I opted to use `compile` and `exec` from within the CPython context to compile and execute code. There's probably more overhead to that than the internal API had, but that overhead should be consistent per benchmark. If anyone cares about hyperoptimizing benchmarks then they can optimize the harness as well.
1 parent 9693ad9 commit ea1f72e

File tree

2 files changed

+87
-140
lines changed

2 files changed

+87
-140
lines changed

benches/execution.rs

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,32 @@
11
use criterion::measurement::WallTime;
22
use criterion::{
3-
criterion_group, criterion_main, Bencher, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
3+
black_box, criterion_group, criterion_main, Bencher, BenchmarkGroup, BenchmarkId, Criterion,
4+
Throughput,
45
};
56
use rustpython_compiler::Mode;
67
use rustpython_parser::ast;
78
use rustpython_parser::Parse;
8-
use rustpython_vm::{Interpreter, PyResult};
9+
use rustpython_vm::{Interpreter, PyResult, Settings};
910
use std::collections::HashMap;
1011
use std::path::Path;
1112

1213
fn bench_cpython_code(b: &mut Bencher, source: &str) {
13-
let gil = cpython::Python::acquire_gil();
14-
let python = gil.python();
15-
16-
b.iter(|| {
17-
let res: cpython::PyResult<()> = python.run(source, None, None);
18-
if let Err(e) = res {
19-
e.print(python);
20-
panic!("Error running source")
21-
}
22-
});
14+
pyo3::Python::with_gil(|py| {
15+
b.iter(|| {
16+
let module =
17+
pyo3::types::PyModule::from_code(py, source, "", "").expect("Error running source");
18+
black_box(module);
19+
})
20+
})
2321
}
2422

2523
fn bench_rustpy_code(b: &mut Bencher, name: &str, source: &str) {
2624
// NOTE: Take long time.
27-
Interpreter::without_stdlib(Default::default()).enter(|vm| {
25+
let mut settings = Settings::default();
26+
settings.path_list.push("Lib/".to_string());
27+
settings.dont_write_bytecode = true;
28+
settings.no_user_site = true;
29+
Interpreter::without_stdlib(settings).enter(|vm| {
2830
// Note: bench_cpython is both compiling and executing the code.
2931
// As such we compile the code in the benchmark loop as well.
3032
b.iter(|| {
@@ -36,16 +38,12 @@ fn bench_rustpy_code(b: &mut Bencher, name: &str, source: &str) {
3638
})
3739
}
3840

39-
pub fn benchmark_file_execution(
40-
group: &mut BenchmarkGroup<WallTime>,
41-
name: &str,
42-
contents: &String,
43-
) {
41+
pub fn benchmark_file_execution(group: &mut BenchmarkGroup<WallTime>, name: &str, contents: &str) {
4442
group.bench_function(BenchmarkId::new(name, "cpython"), |b| {
45-
bench_cpython_code(b, &contents)
43+
bench_cpython_code(b, contents)
4644
});
4745
group.bench_function(BenchmarkId::new(name, "rustpython"), |b| {
48-
bench_rustpy_code(b, name, &contents)
46+
bench_rustpy_code(b, name, contents)
4947
});
5048
}
5149

@@ -55,44 +53,20 @@ pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str,
5553
b.iter(|| ast::Suite::parse(contents, name).unwrap())
5654
});
5755
group.bench_function(BenchmarkId::new("cpython", name), |b| {
58-
let gil = cpython::Python::acquire_gil();
59-
let py = gil.python();
60-
61-
let code = std::ffi::CString::new(contents).unwrap();
62-
let fname = cpython::PyString::new(py, name);
63-
64-
b.iter(|| parse_program_cpython(py, &code, &fname))
56+
pyo3::Python::with_gil(|py| {
57+
let builtins =
58+
pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins");
59+
let compile = builtins.getattr("compile").expect("no compile in builtins");
60+
b.iter(|| {
61+
let x = compile
62+
.call1((contents, name, "exec"))
63+
.expect("Failed to parse code");
64+
black_box(x);
65+
})
66+
})
6567
});
6668
}
6769

68-
fn parse_program_cpython(
69-
py: cpython::Python<'_>,
70-
code: &std::ffi::CStr,
71-
fname: &cpython::PyString,
72-
) {
73-
extern "C" {
74-
fn PyArena_New() -> *mut python3_sys::PyArena;
75-
fn PyArena_Free(arena: *mut python3_sys::PyArena);
76-
}
77-
use cpython::PythonObject;
78-
let fname = fname.as_object();
79-
unsafe {
80-
let arena = PyArena_New();
81-
assert!(!arena.is_null());
82-
let ret = python3_sys::PyParser_ASTFromStringObject(
83-
code.as_ptr() as _,
84-
fname.as_ptr(),
85-
python3_sys::Py_file_input,
86-
std::ptr::null_mut(),
87-
arena,
88-
);
89-
if ret.is_null() {
90-
cpython::PyErr::fetch(py).print(py);
91-
}
92-
PyArena_Free(arena);
93-
}
94-
}
95-
9670
pub fn benchmark_pystone(group: &mut BenchmarkGroup<WallTime>, contents: String) {
9771
// Default is 50_000. This takes a while, so reduce it to 30k.
9872
for idx in (10_000..=30_000).step_by(10_000) {

benches/microbenchmarks.rs

Lines changed: 58 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use criterion::{
55
use rustpython_compiler::Mode;
66
use rustpython_vm::{AsObject, Interpreter, PyResult, Settings};
77
use std::{
8-
ffi, fs, io,
8+
fs, io,
99
path::{Path, PathBuf},
1010
};
1111

@@ -36,95 +36,68 @@ pub struct MicroBenchmark {
3636
}
3737

3838
fn bench_cpython_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) {
39-
let gil = cpython::Python::acquire_gil();
40-
let py = gil.python();
41-
42-
let setup_code = ffi::CString::new(&*bench.setup).unwrap();
43-
let setup_name = ffi::CString::new(format!("{}_setup", bench.name)).unwrap();
44-
let setup_code = cpy_compile_code(py, &setup_code, &setup_name).unwrap();
45-
46-
let code = ffi::CString::new(&*bench.code).unwrap();
47-
let name = ffi::CString::new(&*bench.name).unwrap();
48-
let code = cpy_compile_code(py, &code, &name).unwrap();
49-
50-
let bench_func = |(globals, locals): &mut (cpython::PyDict, cpython::PyDict)| {
51-
let res = cpy_run_code(py, &code, globals, locals);
52-
if let Err(e) = res {
53-
e.print(py);
54-
panic!("Error running microbenchmark")
55-
}
56-
};
57-
58-
let bench_setup = |iterations| {
59-
let globals = cpython::PyDict::new(py);
60-
// setup the __builtins__ attribute - no other way to do this (other than manually) as far
61-
// as I can tell
62-
let _ = py.run("", Some(&globals), None);
63-
let locals = cpython::PyDict::new(py);
64-
if let Some(idx) = iterations {
65-
globals.set_item(py, "ITERATIONS", idx).unwrap();
66-
}
39+
pyo3::Python::with_gil(|py| {
40+
let setup_name = format!("{}_setup", bench.name);
41+
let setup_code = cpy_compile_code(py, &bench.setup, &setup_name).unwrap();
42+
43+
let code = cpy_compile_code(py, &bench.code, &bench.name).unwrap();
44+
45+
// Grab the exec function in advance so we don't have lookups in the hot code
46+
let builtins =
47+
pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins");
48+
let exec = builtins.getattr("exec").expect("no exec in builtins");
49+
50+
let bench_func = |(globals, locals): &mut (&pyo3::types::PyDict, &pyo3::types::PyDict)| {
51+
let res = exec.call((code, &*globals, &*locals), None);
52+
if let Err(e) = res {
53+
e.print(py);
54+
panic!("Error running microbenchmark")
55+
}
56+
};
6757

68-
let res = cpy_run_code(py, &setup_code, &globals, &locals);
69-
if let Err(e) = res {
70-
e.print(py);
71-
panic!("Error running microbenchmark setup code")
72-
}
73-
(globals, locals)
74-
};
75-
76-
if bench.iterate {
77-
for idx in (100..=1_000).step_by(200) {
78-
group.throughput(Throughput::Elements(idx as u64));
79-
group.bench_with_input(BenchmarkId::new("cpython", &bench.name), &idx, |b, idx| {
80-
b.iter_batched_ref(
81-
|| bench_setup(Some(*idx)),
82-
bench_func,
83-
BatchSize::LargeInput,
84-
);
85-
});
86-
}
87-
} else {
88-
group.bench_function(BenchmarkId::new("cpython", &bench.name), move |b| {
89-
b.iter_batched_ref(|| bench_setup(None), bench_func, BatchSize::LargeInput);
90-
});
91-
}
F438
92-
}
58+
let bench_setup = |iterations| {
59+
let globals = pyo3::types::PyDict::new(py);
60+
let locals = pyo3::types::PyDict::new(py);
61+
if let Some(idx) = iterations {
62+
globals.set_item("ITERATIONS", idx).unwrap();
63+
}
9364

94-
unsafe fn cpy_res(
95-
py: cpython::Python<'_>,
96-
x: *mut python3_sys::PyObject,
97-
) -> cpython::PyResult<cpython::PyObject> {
98-
cpython::PyObject::from_owned_ptr_opt(py, x).ok_or_else(|| cpython::PyErr::fetch(py))
99-
}
65+
let res = exec.call((setup_code, &globals, &locals), None);
66+
if let Err(e) = res {
67+
e.print(py);
68+
panic!("Error running microbenchmark setup code")
69+
}
70+
(globals, locals)
71+
};
10072

101-
fn cpy_compile_code(
102-
py: cpython::Python<'_>,
103-
s: &ffi::CStr,
104-
fname: &ffi::CStr,
105-
) -> cpython::PyResult<cpython::PyObject> {
106-
unsafe {
107-
let res =
108-
python3_sys::Py_CompileString(s.as_ptr(), fname.as_ptr(), python3_sys::Py_file_input);
109-
cpy_res(py, res)
110-
}
73+
if bench.iterate {
74+
for idx in (100..=1_000).step_by(200) {
75+
group.throughput(Throughput::Elements(idx as u64));
76+
group.bench_with_input(BenchmarkId::new("cpython", &bench.name), &idx, |b, idx| {
77+
b.iter_batched_ref(
78+
|| bench_setup(Some(*idx)),
79+
bench_func,
80+
BatchSize::LargeInput,
81+
);
82+
});
83+
}
84+
} else {
85+
group.bench_function(BenchmarkId::new("cpython", &bench.name), move |b| {
86+
b.iter_batched_ref(|| bench_setup(None), bench_func, BatchSize::LargeInput);
87+
});
88+
}
89+
})
11190
}
11291

113-
fn cpy_run_code(
114-
py: cpython::Python<'_>,
115-
code: &cpython::PyObject,
116-
locals: &cpython::PyDict,
117-
globals: &cpython::PyDict,
118-
) -> cpython::PyResult<cpython::PyObject> {
119-
use cpython::PythonObject;
120-
unsafe {
121-
let res = python3_sys::PyEval_EvalCode(
122-
code.as_ptr(),
123-
locals.as_object().as_ptr(),
124-
globals.as_object().as_ptr(),
125-
);
126-
cpy_res(py, res)
127-
}
92+
fn cpy_compile_code<'a>(
93+
py: pyo3::Python<'a>,
94+
code: &str,
95+
name: &str,
96+
) -> pyo3::PyResult<&'a pyo3::types::PyCode> {
97+
let builtins =
98+
pyo3::types::PyModule::import(py, "builtins").expect("Failed to import builtins");
99+
let compile = builtins.getattr("compile").expect("no compile in builtins");
100+
compile.call1((code, name, "exec"))?.extract()
128101
}
129102

130103
fn bench_rustpy_code(group: &mut BenchmarkGroup<WallTime>, bench: &MicroBenchmark) {

0 commit comments

Comments
 (0)
0