Skip to content

Commit d6c8247

Browse files
authored
Merge pull request RustPython#2521 from RustPython/fix-cpython-parse-bench
Level the playing field for the parse_to_ast benchmarks
2 parents cba3a25 + f1f7e8d commit d6c8247

File tree

3 files changed

+47
-32
lines changed

3 files changed

+47
-32
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ rustyline = "6.0"
4545

4646
[dev-dependencies]
4747
cpython = "0.5.0"
48+
python3-sys = "0.5.0"
4849
criterion = "0.3"
4950

5051
[[bench]]

benches/execution.rs

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ use rustpython_parser::parser::parse_program;
77
use rustpython_vm::pyobject::PyResult;
88
use rustpython_vm::Interpreter;
99
use std::collections::HashMap;
10-
use std::path::{Path, PathBuf};
11-
use std::{fs, io};
10+
use std::path::Path;
1211

1312
fn bench_cpython_code(b: &mut Bencher, source: &str) {
1413
let gil = cpython::Python::acquire_gil();
@@ -50,32 +49,50 @@ pub fn benchmark_file_execution(
5049
});
5150
}
5251

53-
pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str, contents: &String) {
52+
pub fn benchmark_file_parsing(group: &mut BenchmarkGroup<WallTime>, name: &str, contents: &str) {
5453
group.throughput(Throughput::Bytes(contents.len() as u64));
5554
group.bench_function(BenchmarkId::new("rustpython", name), |b| {
5655
b.iter(|| parse_program(contents).unwrap())
5756
});
5857
group.bench_function(BenchmarkId::new("cpython", name), |b| {
5958
let gil = cpython::Python::acquire_gil();
60-
let python = gil.python();
59+
let py = gil.python();
6160

62-
let globals = None;
63-
let locals = cpython::PyDict::new(python);
61+
let code = std::ffi::CString::new(contents).unwrap();
62+
let fname = cpython::PyString::new(py, name);
6463

65-
locals.set_item(python, "SOURCE_CODE", &contents).unwrap();
66-
67-
let code = "compile(SOURCE_CODE, mode=\"exec\", filename=\"minidom.py\")";
68-
b.iter(|| {
69-
let res: cpython::PyResult<cpython::PyObject> =
70-
python.eval(code, globals, Some(&locals));
71-
if let Err(e) = res {
72-
e.print(python);
73-
panic!("Error compiling source")
74-
}
75-
})
64+
b.iter(|| parse_program_cpython(py, &code, &fname))
7665
});
7766
}
7867

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+
7996
pub fn benchmark_pystone(group: &mut BenchmarkGroup<WallTime>, contents: String) {
8097
// Default is 50_000. This takes a while, so reduce it to 30k.
8198
for idx in (10_000..=30_000).step_by(10_000) {
@@ -94,39 +111,35 @@ pub fn benchmark_pystone(group: &mut BenchmarkGroup<WallTime>, contents: String)
94111

95112
pub fn criterion_benchmark(c: &mut Criterion) {
96113
let benchmark_dir = Path::new("./benches/benchmarks/");
97-
let dirs: Vec<fs::DirEntry> = benchmark_dir
114+
let mut benches = benchmark_dir
98115
.read_dir()
99116
.unwrap()
100-
.collect::<io::Result<_>>()
101-
.unwrap();
102-
let paths: Vec<PathBuf> = dirs.iter().map(|p| p.path()).collect();
103-
104-
let mut name_to_contents: HashMap<String, String> = paths
105-
.into_iter()
106-
.map(|p| {
107-
let name = p.file_name().unwrap().to_os_string();
108-
let contents = fs::read_to_string(p).unwrap();
109-
(name.into_string().unwrap(), contents)
117+
.map(|entry| {
118+
let path = entry.unwrap().path();
119+
(
120+
path.file_name().unwrap().to_str().unwrap().to_owned(),
121+
std::fs::read_to_string(path).unwrap(),
122+
)
110123
})
111-
.collect();
124+
.collect::<HashMap<_, _>>();
112125

113126
// Benchmark parsing
114127
let mut parse_group = c.benchmark_group("parse_to_ast");
115-
for (name, contents) in name_to_contents.iter() {
128+
for (name, contents) in &benches {
116129
benchmark_file_parsing(&mut parse_group, name, contents);
117130
}
118131
parse_group.finish();
119132

120133
// Benchmark PyStone
121-
if let Some(pystone_contents) = name_to_contents.remove("pystone.py") {
134+
if let Some(pystone_contents) = benches.remove("pystone.py") {
122135
let mut pystone_group = c.benchmark_group("pystone");
123136
benchmark_pystone(&mut pystone_group, pystone_contents);
124137
pystone_group.finish();
125138
}
126139

127140
// Benchmark execution
128141
let mut execution_group = c.benchmark_group("execution");
129-
for (name, contents) in name_to_contents.iter() {
142+
for (name, contents) in &benches {
130143
benchmark_file_execution(&mut execution_group, name, contents);
131144
}
132145
execution_group.finish();

0 commit comments

Comments
 (0)