Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
191 changes: 191 additions & 0 deletions benches/codspeed/embed_benchmarks.rs
Original file line number Diff line number Diff line change
@@ -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<String> = (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);
Loading