diff --git a/.github/workflows/codspeed.yml b/.github/workflows/codspeed.yml new file mode 100644 index 000000000..76a448dc1 --- /dev/null +++ b/.github/workflows/codspeed.yml @@ -0,0 +1,74 @@ +name: CodSpeed + +on: + push: + branches: master + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + id-token: write + +env: + clang: "17" + php_version: "8.4" + +jobs: + codspeed: + name: Run CodSpeed Benchmarks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ env.php_version }} + env: + debug: true + + - name: Install libphp-embed + run: sudo apt-get update && sudo apt-get install -y libphp${{ env.php_version }}-embed + + - name: Setup Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + + - name: Cache cargo dependencies + uses: Swatinem/rust-cache@v2 + + - name: Cache LLVM and Clang + id: cache-llvm + uses: actions/cache@v5 + with: + path: ${{ runner.temp }}/llvm-${{ env.clang }} + key: ubuntu-latest-llvm-${{ env.clang }} + + - name: Setup LLVM & Clang + uses: KyleMayes/install-llvm-action@v2 + with: + version: ${{ env.clang }} + directory: ${{ runner.temp }}/llvm-${{ env.clang }} + cached: ${{ steps.cache-llvm.outputs.cache-hit }} + + - name: Configure Clang + run: | + echo "LIBCLANG_PATH=${{ runner.temp }}/llvm-${{ env.clang }}/lib" >> $GITHUB_ENV + + - name: Install cargo-codspeed + run: cargo install cargo-codspeed --locked + + - name: Build benchmarks + run: cargo codspeed build --features embed + + - name: Run CodSpeed benchmarks + uses: CodSpeedHQ/action@v4 + with: + mode: simulation + run: cargo codspeed run diff --git a/Cargo.toml b/Cargo.toml index 654716fa2..c002ed700 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ ext-php-rs-derive = { version = "=0.11.11", path = "./crates/macros" } [dev-dependencies] skeptic = "0.13" +criterion = { package = "codspeed-criterion-compat", version = "4" } [build-dependencies] anyhow = "1" @@ -95,3 +96,9 @@ path = "tests/module.rs" [[test]] name = "sapi_tests" path = "tests/sapi.rs" + +[[bench]] +name = "embed_benchmarks" +path = "benches/codspeed/embed_benchmarks.rs" +harness = false +required-features = ["embed"] diff --git a/README.md b/README.md index 3544f4558..f8abb6452 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![docs.rs](https://img.shields.io/docsrs/ext-php-rs/latest)](https://docs.rs/ext-php-rs) [![Guide Workflow Status](https://img.shields.io/github/actions/workflow/status/extphprs/ext-php-rs/docs.yml?branch=master&label=guide)](https://ext-php.rs) ![CI Workflow Status](https://img.shields.io/github/actions/workflow/status/extphprs/ext-php-rs/master.yml?branch=master) -[![Coverage Status](https://coveralls.io/repos/github/extphprs/ext-php-rs/badge.svg?branch=master)](https://coveralls.io/github/extphprs/ext-php-rs?branch=master) [![Discord](https://img.shields.io/discord/115233111977099271)](https://discord.gg/dphp) +[![Coverage Status](https://coveralls.io/repos/github/extphprs/ext-php-rs/badge.svg?branch=master)](https://coveralls.io/github/extphprs/ext-php-rs?branch=master) [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/extphprs/ext-php-rs?utm_source=badge) [![Discord](https://img.shields.io/discord/115233111977099271)](https://discord.gg/dphp) Bindings and abstractions for the Zend API to build PHP extensions natively in Rust. diff --git a/benches/codspeed/embed_benchmarks.rs b/benches/codspeed/embed_benchmarks.rs new file mode 100644 index 000000000..0f54390d8 --- /dev/null +++ b/benches/codspeed/embed_benchmarks.rs @@ -0,0 +1,191 @@ +use std::panic::AssertUnwindSafe; + +use criterion::{Criterion, criterion_group, criterion_main}; +use ext_php_rs::embed::Embed; +use ext_php_rs::types::{ZendHashTable, ZendStr, Zval}; + +fn bench_eval_simple(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("eval_simple_expression", |b| { + b.iter(|| { + let result = Embed::eval("1 + 1;"); + assert!(result.is_ok()); + }); + }); + }); +} + +fn bench_eval_string_concat(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("eval_string_concat", |b| { + b.iter(|| { + let result = Embed::eval("'hello' . ' ' . 'world';"); + assert!(result.is_ok()); + }); + }); + }); +} + +fn bench_eval_array_creation(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("eval_array_creation", |b| { + b.iter(|| { + let result = Embed::eval("[1, 2, 3, 4, 5];"); + assert!(result.is_ok()); + }); + }); + }); +} + +fn bench_hashtable_insert_sequential(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("hashtable_push_100", |b| { + b.iter(|| { + let mut ht = ZendHashTable::new(); + for i in 0..100 { + ht.push(i as i64).unwrap(); + } + }); + }); + }); +} + +fn bench_hashtable_insert_string_keys(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + let keys: Vec = (0..100).map(|i| format!("key_{i}")).collect(); + + c.bench_function("hashtable_insert_string_keys_100", |b| { + b.iter(|| { + let mut ht = ZendHashTable::new(); + for (i, key) in keys.iter().enumerate() { + ht.insert(key.as_str(), i as i64).unwrap(); + } + }); + }); + }); +} + +fn bench_hashtable_get(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + let mut ht = ZendHashTable::new(); + for i in 0..100 { + ht.insert(format!("key_{i}").as_str(), i as i64).unwrap(); + } + + c.bench_function("hashtable_get_by_string_key", |b| { + b.iter(|| { + let _ = ht.get("key_50"); + }); + }); + }); +} + +fn bench_hashtable_get_index(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + let mut ht = ZendHashTable::new(); + for i in 0..100 { + ht.push(i as i64).unwrap(); + } + + c.bench_function("hashtable_get_by_index", |b| { + b.iter(|| { + let _ = ht.get_index(50); + }); + }); + }); +} + +fn bench_zend_string_creation(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("zend_string_create_short", |b| { + b.iter(|| { + let _s = ZendStr::new("hello world", false); + }); + }); + }); +} + +fn bench_zend_string_creation_long(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + let long_string = "a".repeat(1000); + + c.bench_function("zend_string_create_long", |b| { + b.iter(|| { + let _s = ZendStr::new(&long_string, false); + }); + }); + }); +} + +fn bench_zval_type_conversions(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("zval_set_and_read_long", |b| { + b.iter(|| { + let mut zv = Zval::new(); + zv.set_long(42); + let _ = zv.long(); + }); + }); + }); +} + +fn bench_zval_string_roundtrip(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + c.bench_function("zval_string_roundtrip", |b| { + b.iter(|| { + let mut zv = Zval::new(); + let _ = zv.set_string("hello world", false); + let _ = zv.str(); + }); + }); + }); +} + +fn bench_hashtable_iteration(c: &mut Criterion) { + let c = AssertUnwindSafe(c); + Embed::run(|| { + let mut ht = ZendHashTable::new(); + for i in 0..100 { + ht.insert(format!("key_{i}").as_str(), i as i64).unwrap(); + } + + c.bench_function("hashtable_iterate_100_entries", |b| { + b.iter(|| { + let mut count = 0; + for (_key, _val) in ht.iter() { + count += 1; + } + assert_eq!(count, 100); + }); + }); + }); +} + +criterion_group!( + benches, + bench_eval_simple, + bench_eval_string_concat, + bench_eval_array_creation, + bench_hashtable_insert_sequential, + bench_hashtable_insert_string_keys, + bench_hashtable_get, + bench_hashtable_get_index, + bench_zend_string_creation, + bench_zend_string_creation_long, + bench_zval_type_conversions, + bench_zval_string_roundtrip, + bench_hashtable_iteration, +); + +criterion_main!(benches);