diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 00000000000..01e2731f5ad --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,56 @@ +name: CodSpeed + +on: + push: + branches: ["main"] + paths: + - "Cargo.toml" + - "Cargo.lock" + - "majit/**/*" + - "pyre/**/*" + - ".github/workflows/codspeed.yml" + pull_request: + branches: ["main"] + paths: + - "Cargo.toml" + - "Cargo.lock" + - "majit/**/*" + - "pyre/**/*" + - ".github/workflows/codspeed.yml" + workflow_dispatch: + +concurrency: + group: codspeed-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +permissions: + contents: read + id-token: write + +jobs: + codspeed: + name: Run benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Setup Rust toolchain + uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable + with: + components: rustfmt + + - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 + with: + cache-bin: false + + - name: Install cargo-codspeed + run: cargo install cargo-codspeed + + - name: Build the benchmarks + run: cargo codspeed build -p pyre-bench + + - name: Run the benchmarks + uses: CodSpeedHQ/action@3194d9a39c4d46684cb44bf7207fc56626aad8fd # v4 + with: + mode: simulation + run: cargo codspeed run -p pyre-bench diff --git a/Cargo.toml b/Cargo.toml index d4ccb47e166..50a46846dcd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ "pyre/pyrex", "pyre/pyre-wasm", "pyre/pyre-wasm-test", + "pyre/pyre-bench", ] [workspace.dependencies] diff --git a/pyre/README.md b/pyre/README.md index e0a8aef6999..9d493550e0c 100644 --- a/pyre/README.md +++ b/pyre/README.md @@ -1,6 +1,7 @@ # pyre [![Discord](https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white)](https://discord.gg/2fAUZ49JX3) +[![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/youknowone/pyre?utm_source=badge) **Py**thon **Re**written — a no-GIL Python implementation in Rust, with a meta-tracing JIT compiler ported from PyPy. diff --git a/pyre/pyre-bench/Cargo.toml b/pyre/pyre-bench/Cargo.toml new file mode 100644 index 00000000000..63eb700cb2a --- /dev/null +++ b/pyre/pyre-bench/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "pyre-bench" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Performance benchmarks for the pyre interpreter" +publish = false + +[dev-dependencies] +criterion = { package = "codspeed-criterion-compat", version = "4.6.0" } +pyre-object = { workspace = true } +pyre-interpreter = { workspace = true } +pyre-jit = { workspace = true } + +[[bench]] +name = "interpreter" +harness = false diff --git a/pyre/pyre-bench/benches/interpreter.rs b/pyre/pyre-bench/benches/interpreter.rs new file mode 100644 index 00000000000..888b15a464a --- /dev/null +++ b/pyre/pyre-bench/benches/interpreter.rs @@ -0,0 +1,160 @@ +//! Benchmarks for the pyre interpreter. +//! +//! Each benchmark compiles and executes a small Python script through the +//! full interpreter + JIT pipeline, measuring end-to-end execution time. + +use std::rc::Rc; + +use criterion::{Criterion, criterion_group, criterion_main}; + +use pyre_interpreter::call::{register_build_class, set_build_class_exec_ctx, set_last_exec_ctx}; +use pyre_interpreter::pyframe::PyFrame; +use pyre_interpreter::{Mode, PyExecutionContext, compile_source_with_filename}; +use pyre_jit::eval::eval_with_jit; + +/// Suppress print() output during benchmarks. +fn silence_print() { + pyre_interpreter::set_print_hook(|_| {}); +} + +/// Execute a Python source string through the full interpreter + JIT pipeline. +fn run_python(source: &str, filename: &str) { + let code = compile_source_with_filename(source, Mode::Exec, filename) + .expect("benchmark script must compile"); + + register_build_class(); + + let execution_context = Rc::new(PyExecutionContext::default()); + set_build_class_exec_ctx(Rc::as_ptr(&execution_context)); + set_last_exec_ctx(Rc::as_ptr(&execution_context)); + + let mut frame = PyFrame::new_with_context(code, execution_context) + .expect("benchmark frame creation must succeed"); + + let _ = eval_with_jit(&mut frame); +} + +// --------------------------------------------------------------------------- +// Python benchmark scripts (with reduced iteration counts for CI) +// --------------------------------------------------------------------------- + +const INT_LOOP: &str = r#" +def main(): + s = 0 + i = 0 + while i < 500000: + s = s + i + i = i + 1 + +main() +"#; + +const FIB_LOOP: &str = r#" +def fib(n): + a = 0 + b = 1 + i = 0 + while i < n: + t = a + b + a = b + b = t + i = i + 1 + return b + +fib(5000) +"#; + +const FIB_RECURSIVE: &str = r#" +def fib(n): + if n < 2: + return n + return fib(n - 1) + fib(n - 2) + +fib(20) +"#; + +const INLINE_HELPER: &str = r#" +def add(a, b): + return a + b + +def mul(a, b): + return a * b + +def square(x): + return mul(x, x) + +def compute(x): + return add(square(x), x) + +def main(): + s = 0 + i = 0 + while i < 200000: + s = add(s, compute(i)) % 1000000007 + i = add(i, 1) + +main() +"#; + +const NESTED_LOOP: &str = r#" +def main(): + s = 0 + i = 0 + while i < 500: + j = 0 + while j < 500: + s = s + i * j + j = j + 1 + i = i + 1 + +main() +"#; + +const FLOAT_LOOP: &str = r#" +def main(): + s = 0.0 + i = 0 + while i < 500000: + s = s + i * 0.1 + i = i + 1 + +main() +"#; + +// --------------------------------------------------------------------------- +// Benchmark registration +// --------------------------------------------------------------------------- + +fn bench_interpreter(c: &mut Criterion) { + silence_print(); + pyre_jit::eval::init_jit_hooks(); + pyre_interpreter::stack_check::set_recursion_limit(5000) + .expect("startup recursion limit must be applicable"); + + c.bench_function("int_loop", |b| { + b.iter(|| run_python(INT_LOOP, "int_loop.py")); + }); + + c.bench_function("fib_loop", |b| { + b.iter(|| run_python(FIB_LOOP, "fib_loop.py")); + }); + + c.bench_function("fib_recursive", |b| { + b.iter(|| run_python(FIB_RECURSIVE, "fib_recursive.py")); + }); + + c.bench_function("inline_helper", |b| { + b.iter(|| run_python(INLINE_HELPER, "inline_helper.py")); + }); + + c.bench_function("nested_loop", |b| { + b.iter(|| run_python(NESTED_LOOP, "nested_loop.py")); + }); + + c.bench_function("float_loop", |b| { + b.iter(|| run_python(FLOAT_LOOP, "float_loop.py")); + }); +} + +criterion_group!(benches, bench_interpreter); +criterion_main!(benches);