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
57 changes: 57 additions & 0 deletions .github/workflows/codspeed.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: CodSpeed Benchmarks

on:
push:
branches:
- "main"
pull_request:
# `workflow_dispatch` allows CodSpeed to trigger backtest
# performance analysis in order to generate initial data.
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}
cancel-in-progress: true

permissions: {}

env:
CODSPEED_PERF_ENABLED: false

jobs:
benchmarks:
name: Run benchmarks
runs-on: codspeed-macro
permissions:
contents: read # required for actions/checkout
id-token: write # required for OIDC authentication with CodSpeed

steps:
- name: Checkout
uses: actions/checkout@v6
with:
persist-credentials: false
- name: Install Rust
run: |
rustup update stable
rustup target add wasm32-unknown-unknown
- name: Install TSC
run: npm install -g typescript
- name: Update Node.js
uses: actions/setup-node@v6
with:
node-version: latest
- uses: Swatinem/rust-cache@v2
with:
workspaces: |
client
host
common/wcodspeed
- name: Run client benchmarks
working-directory: client
run: cargo bench --workspace
- name: Upload benchmark results
uses: CodSpeedHQ/action@v4
with:
mode: walltime
run: cargo run --manifest-path common/wcodspeed/Cargo.toml client/target/benchmark.json
4 changes: 4 additions & 0 deletions client/js-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ bench = false
doctest = false
test = false

[[bench]]
harness = false
name = "conv"

[dependencies]
js-bindgen = { workspace = true }
js-sys-macro = { workspace = true, optional = true }
Expand Down
35 changes: 35 additions & 0 deletions client/js-sys/benches/conv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use js_bindgen_test::{Criterion, criterion_group, criterion_main};
use js_sys::js_sys;

js_bindgen::embed_js!(module = "conv", name = "bench", "(value) => value");

fn bench_conv_u128(c: &mut Criterion) {
#[js_sys]
extern "js-sys" {
#[js_sys(js_embed = "bench")]
fn conv_u128(value: u128) -> u128;
}

c.bench_function("bench_conv_u128", |b| {
b.iter(|| {
assert_eq!(conv_u128(4242), 4242);
});
});
}

fn bench_conv_i128(c: &mut Criterion) {
#[js_sys]
extern "js-sys" {
#[js_sys(js_embed = "bench")]
fn conv_i128(value: i128) -> i128;
}

c.bench_function("bench_conv_i128", |b| {
b.iter(|| {
assert_eq!(conv_i128(4242), 4242);
});
});
}

criterion_group!(benches, bench_conv_u128, bench_conv_i128);
criterion_main!(benches);
20 changes: 16 additions & 4 deletions client/js-sys/tests/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ fn js_value() {

let rust_array = [JsValue::UNDEFINED; 42];
let js_array = JsArray::from(&rust_array);
assert_eq!(rust_array.len(), js_array.length().try_into().unwrap());
assert_eq!(
rust_array.len(),
usize::try_from(js_array.length()).unwrap()
);

let ffi_array = js(&rust_array);
assert_eq!(rust_array.len(), ffi_array.length().try_into().unwrap());
assert_eq!(
rust_array.len(),
usize::try_from(js_array.length()).unwrap()
);

let returned_array: [JsValue; 42] = js_array.to_array().unwrap();
assert_eq!(rust_array, returned_array);
Expand All @@ -37,10 +43,16 @@ fn u32() {

let rust_array: [u32; 42] = array::from_fn(|i| i.try_into().unwrap());
let js_array = JsArray::from(&rust_array);
assert_eq!(rust_array.len(), js_array.length().try_into().unwrap());
assert_eq!(
rust_array.len(),
usize::try_from(js_array.length()).unwrap()
);

let ffi_array = u32(&rust_array);
assert_eq!(rust_array.len(), ffi_array.length().try_into().unwrap());
assert_eq!(
rust_array.len(),
usize::try_from(ffi_array.length()).unwrap()
);

let returned_array: [u32; 42] = js_array.to_array().unwrap();
assert_eq!(rust_array, returned_array);
Expand Down
10 changes: 10 additions & 0 deletions client/test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,15 @@ test = false
js-bindgen-test-macro = { workspace = true }
js-sys = { workspace = true, features = ["macro"] }

async-trait = "0.1.89"
cast = "0.3"
libm = "0.2.11"
nu-ansi-term = { version = "0.50", default-features = false }
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
once_cell = "1.21.4"
oorandom = "11.1.5"
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }

[lints]
workspace = true
157 changes: 157 additions & 0 deletions client/test/src/criterion/analysis.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
use alloc::vec::Vec;

use super::benchmark::BenchmarkConfig;
use super::estimate::{
ConfidenceInterval, Distributions, Estimate, Estimates, PointEstimates, build_estimates,
};
use super::measurement::Measurement;
use super::report::{BenchmarkId, Report};
use super::routine::Routine;
use super::stats::bivariate::Data;
use super::stats::bivariate::regression::Slope;
use super::stats::univariate::Sample;
use super::stats::{Distribution, Tails};
use super::{Criterion, SavedSample, baseline, compare};

// Common analysis procedure
pub(crate) async fn common<M: Measurement>(
id: &BenchmarkId,
routine: &mut dyn Routine<M>,
config: &BenchmarkConfig,
criterion: &Criterion<M>,
) {
criterion.report.benchmark_start(id);

let (sampling_mode, iters, times);
let sample = routine
.sample(&criterion.measurement, id, config, criterion)
.await;
sampling_mode = sample.0;
iters = sample.1;
times = sample.2;

criterion.report.analysis(id);

if times.contains(&0.0) {
return;
}

let avg_times = iters
.iter()
.zip(times.iter())
.map(|(&iters, &elapsed)| elapsed / iters)
.collect::<Vec<f64>>();
let avg_times = Sample::new(&avg_times);
let labeled_sample = super::stats::univariate::outliers::tukey::classify(avg_times);

let data = Data::new(&iters, &times);
let (mut distributions, mut estimates) = estimates(avg_times, config);
if sampling_mode.is_linear() {
let (distribution, slope) = regression(&data, config);

estimates.slope = Some(slope);
distributions.slope = Some(distribution);
}

let comparison = compare::common(id, avg_times, config).map(
|(t_value, t_distribution, relative_estimates, ..)| {
let p_value = t_distribution.p_value(t_value, Tails::Two);
super::report::ComparisonData {
p_value,
relative_estimates,
significance_threshold: config.significance_level,
noise_threshold: config.noise_threshold,
}
},
);

let measurement_data = super::report::MeasurementData {
avg_times: labeled_sample,
absolute_estimates: estimates.clone(),
comparison,
};

criterion
.report
.measurement_complete(id, &measurement_data, criterion.measurement.formatter());

baseline::write(
id.desc(),
baseline::BenchmarkBaseline {
file: criterion.location.as_ref().map(|l| l.file.clone()),
module_path: criterion.location.as_ref().map(|l| l.module_path.clone()),
iters: data.x().as_ref().to_vec(),
times: data.y().as_ref().to_vec(),
sample: SavedSample {
sampling_mode,
iters: data.x().as_ref().to_vec(),
times: data.y().as_ref().to_vec(),
},
estimates,
},
);
}

// Performs a simple linear regression on the sample
fn regression(
data: &Data<'_, f64, f64>,
config: &BenchmarkConfig,
) -> (Distribution<f64>, Estimate) {
let cl = config.confidence_level;

let distribution = data.bootstrap(config.nresamples, |d| (Slope::fit(&d).0,)).0;

let point = Slope::fit(data);
let (lb, ub) = distribution.confidence_interval(config.confidence_level);
let se = distribution.std_dev(None);

(
distribution,
Estimate {
confidence_interval: ConfidenceInterval {
confidence_level: cl,
lower_bound: lb,
upper_bound: ub,
},
point_estimate: point.0,
standard_error: se,
},
)
}

// Estimates the statistics of the population from the sample
fn estimates(avg_times: &Sample<f64>, config: &BenchmarkConfig) -> (Distributions, Estimates) {
fn stats(sample: &Sample<f64>) -> (f64, f64, f64, f64) {
let mean = sample.mean();
let std_dev = sample.std_dev(Some(mean));
let median = sample.percentiles().median();
let mad = sample.median_abs_dev(Some(median));

(mean, std_dev, median, mad)
}

let cl = config.confidence_level;
let nresamples = config.nresamples;

let (mean, std_dev, median, mad) = stats(avg_times);
let points = PointEstimates {
mean,
median,
std_dev,
median_abs_dev: mad,
};

let (dist_mean, dist_stddev, dist_median, dist_mad) = avg_times.bootstrap(nresamples, stats);

let distributions = Distributions {
mean: dist_mean,
slope: None,
median: dist_median,
median_abs_dev: dist_mad,
std_dev: dist_stddev,
};

let estimates = build_estimates(&distributions, &points, cl);

(distributions, estimates)
}
Loading
Loading