Skip to content

Commit f282e8e

Browse files
committed
Add benchmark
1 parent 01774e1 commit f282e8e

60 files changed

Lines changed: 4720 additions & 69 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/codspeed.yaml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: CodSpeed Benchmarks
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
pull_request:
8+
# `workflow_dispatch` allows CodSpeed to trigger backtest
9+
# performance analysis in order to generate initial data.
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref_name }}
14+
cancel-in-progress: true
15+
16+
permissions: {}
17+
18+
env:
19+
CODSPEED_PERF_ENABLED: false
20+
21+
jobs:
22+
benchmarks:
23+
name: Run benchmarks
24+
runs-on: codspeed-macro
25+
permissions:
26+
contents: read # required for actions/checkout
27+
id-token: write # required for OIDC authentication with CodSpeed
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v6
32+
with:
33+
persist-credentials: false
34+
- name: Install Rust
35+
run: |
36+
rustup update stable
37+
rustup target add wasm32-unknown-unknown
38+
- name: Install TSC
39+
run: npm install -g typescript
40+
- name: Update Node.js
41+
uses: actions/setup-node@v6
42+
with:
43+
node-version: latest
44+
- uses: Swatinem/rust-cache@v2
45+
with:
46+
workspaces: |
47+
client
48+
host
49+
common/wcodspeed
50+
- name: Run client benchmarks
51+
working-directory: client
52+
run: cargo bench --workspace
53+
- name: Upload benchmark results
54+
uses: CodSpeedHQ/action@v4
55+
with:
56+
mode: walltime
57+
run: cargo run --manifest-path common/wcodspeed/Cargo.toml client/target/benchmark.json

client/js-sys/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ bench = false
1010
doctest = false
1111
test = false
1212

13+
[[bench]]
14+
harness = false
15+
name = "conv"
16+
1317
[dependencies]
1418
js-bindgen = { workspace = true }
1519
js-sys-macro = { workspace = true, optional = true }

client/js-sys/benches/conv.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use js_bindgen_test::{Criterion, criterion_group, criterion_main};
2+
use js_sys::js_sys;
3+
4+
js_bindgen::embed_js!(module = "conv", name = "bench", "(value) => value");
5+
6+
fn bench_conv_u128(c: &mut Criterion) {
7+
#[js_sys]
8+
extern "js-sys" {
9+
#[js_sys(js_embed = "bench")]
10+
fn conv_u128(value: u128) -> u128;
11+
}
12+
13+
c.bench_function("bench_conv_u128", |b| {
14+
b.iter(|| {
15+
assert_eq!(conv_u128(4242), 4242);
16+
});
17+
});
18+
}
19+
20+
fn bench_conv_i128(c: &mut Criterion) {
21+
#[js_sys]
22+
extern "js-sys" {
23+
#[js_sys(js_embed = "bench")]
24+
fn conv_i128(value: i128) -> i128;
25+
}
26+
27+
c.bench_function("bench_conv_i128", |b| {
28+
b.iter(|| {
29+
assert_eq!(conv_i128(4242), 4242);
30+
});
31+
});
32+
}
33+
34+
criterion_group!(benches, bench_conv_u128, bench_conv_i128);
35+
criterion_main!(benches);

client/js-sys/tests/array.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ fn js_value() {
1515

1616
let rust_array = [JsValue::UNDEFINED; 42];
1717
let js_array = JsArray::from(&rust_array);
18-
assert_eq!(rust_array.len(), js_array.length().try_into().unwrap());
18+
assert_eq!(
19+
rust_array.len(),
20+
usize::try_from(js_array.length()).unwrap()
21+
);
1922

2023
let ffi_array = js(&rust_array);
21-
assert_eq!(rust_array.len(), ffi_array.length().try_into().unwrap());
24+
assert_eq!(
25+
rust_array.len(),
26+
usize::try_from(js_array.length()).unwrap()
27+
);
2228

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

3844
let rust_array: [u32; 42] = array::from_fn(|i| i.try_into().unwrap());
3945
let js_array = JsArray::from(&rust_array);
40-
assert_eq!(rust_array.len(), js_array.length().try_into().unwrap());
46+
assert_eq!(
47+
rust_array.len(),
48+
usize::try_from(js_array.length()).unwrap()
49+
);
4150

4251
let ffi_array = u32(&rust_array);
43-
assert_eq!(rust_array.len(), ffi_array.length().try_into().unwrap());
52+
assert_eq!(
53+
rust_array.len(),
54+
usize::try_from(ffi_array.length()).unwrap()
55+
);
4456

4557
let returned_array: [u32; 42] = js_array.to_array().unwrap();
4658
assert_eq!(rust_array, returned_array);

client/test/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,15 @@ test = false
1414
js-bindgen-test-macro = { workspace = true }
1515
js-sys = { workspace = true, features = ["macro"] }
1616

17+
async-trait = "0.1.89"
18+
cast = "0.3"
19+
libm = "0.2.11"
20+
nu-ansi-term = { version = "0.50", default-features = false }
21+
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
22+
once_cell = "1.21.4"
23+
oorandom = "11.1.5"
24+
serde = { version = "1.0", default-features = false, features = ["derive"] }
25+
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
26+
1727
[lints]
1828
workspace = true
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
use alloc::vec::Vec;
2+
3+
use super::benchmark::BenchmarkConfig;
4+
use super::estimate::{
5+
ConfidenceInterval, Distributions, Estimate, Estimates, PointEstimates, build_estimates,
6+
};
7+
use super::measurement::Measurement;
8+
use super::report::{BenchmarkId, Report};
9+
use super::routine::Routine;
10+
use super::stats::bivariate::Data;
11+
use super::stats::bivariate::regression::Slope;
12+
use super::stats::univariate::Sample;
13+
use super::stats::{Distribution, Tails};
14+
use super::{Criterion, SavedSample, baseline, compare};
15+
16+
// Common analysis procedure
17+
pub(crate) async fn common<M: Measurement>(
18+
id: &BenchmarkId,
19+
routine: &mut dyn Routine<M>,
20+
config: &BenchmarkConfig,
21+
criterion: &Criterion<M>,
22+
) {
23+
criterion.report.benchmark_start(id);
24+
25+
let (sampling_mode, iters, times);
26+
let sample = routine
27+
.sample(&criterion.measurement, id, config, criterion)
28+
.await;
29+
sampling_mode = sample.0;
30+
iters = sample.1;
31+
times = sample.2;
32+
33+
criterion.report.analysis(id);
34+
35+
if times.contains(&0.0) {
36+
return;
37+
}
38+
39+
let avg_times = iters
40+
.iter()
41+
.zip(times.iter())
42+
.map(|(&iters, &elapsed)| elapsed / iters)
43+
.collect::<Vec<f64>>();
44+
let avg_times = Sample::new(&avg_times);
45+
let labeled_sample = super::stats::univariate::outliers::tukey::classify(avg_times);
46+
47+
let data = Data::new(&iters, &times);
48+
let (mut distributions, mut estimates) = estimates(avg_times, config);
49+
if sampling_mode.is_linear() {
50+
let (distribution, slope) = regression(&data, config);
51+
52+
estimates.slope = Some(slope);
53+
distributions.slope = Some(distribution);
54+
}
55+
56+
let comparison = compare::common(id, avg_times, config).map(
57+
|(t_value, t_distribution, relative_estimates, ..)| {
58+
let p_value = t_distribution.p_value(t_value, Tails::Two);
59+
super::report::ComparisonData {
60+
p_value,
61+
relative_estimates,
62+
significance_threshold: config.significance_level,
63+
noise_threshold: config.noise_threshold,
64+
}
65+
},
66+
);
67+
68+
let measurement_data = super::report::MeasurementData {
69+
avg_times: labeled_sample,
70+
absolute_estimates: estimates.clone(),
71+
comparison,
72+
};
73+
74+
criterion
75+
.report
76+
.measurement_complete(id, &measurement_data, criterion.measurement.formatter());
77+
78+
baseline::write(
79+
id.desc(),
80+
baseline::BenchmarkBaseline {
81+
file: criterion.location.as_ref().map(|l| l.file.clone()),
82+
module_path: criterion.location.as_ref().map(|l| l.module_path.clone()),
83+
iters: data.x().as_ref().to_vec(),
84+
times: data.y().as_ref().to_vec(),
85+
sample: SavedSample {
86+
sampling_mode,
87+
iters: data.x().as_ref().to_vec(),
88+
times: data.y().as_ref().to_vec(),
89+
},
90+
estimates,
91+
},
92+
);
93+
}
94+
95+
// Performs a simple linear regression on the sample
96+
fn regression(
97+
data: &Data<'_, f64, f64>,
98+
config: &BenchmarkConfig,
99+
) -> (Distribution<f64>, Estimate) {
100+
let cl = config.confidence_level;
101+
102+
let distribution = data.bootstrap(config.nresamples, |d| (Slope::fit(&d).0,)).0;
103+
104+
let point = Slope::fit(data);
105+
let (lb, ub) = distribution.confidence_interval(config.confidence_level);
106+
let se = distribution.std_dev(None);
107+
108+
(
109+
distribution,
110+
Estimate {
111+
confidence_interval: ConfidenceInterval {
112+
confidence_level: cl,
113+
lower_bound: lb,
114+
upper_bound: ub,
115+
},
116+
point_estimate: point.0,
117+
standard_error: se,
118+
},
119+
)
120+
}
121+
122+
// Estimates the statistics of the population from the sample
123+
fn estimates(avg_times: &Sample<f64>, config: &BenchmarkConfig) -> (Distributions, Estimates) {
124+
fn stats(sample: &Sample<f64>) -> (f64, f64, f64, f64) {
125+
let mean = sample.mean();
126+
let std_dev = sample.std_dev(Some(mean));
127+
let median = sample.percentiles().median();
128+
let mad = sample.median_abs_dev(Some(median));
129+
130+
(mean, std_dev, median, mad)
131+
}
132+
133+
let cl = config.confidence_level;
134+
let nresamples = config.nresamples;
135+
136+
let (mean, std_dev, median, mad) = stats(avg_times);
137+
let points = PointEstimates {
138+
mean,
139+
median,
140+
std_dev,
141+
median_abs_dev: mad,
142+
};
143+
144+
let (dist_mean, dist_stddev, dist_median, dist_mad) = avg_times.bootstrap(nresamples, stats);
145+
146+
let distributions = Distributions {
147+
mean: dist_mean,
148+
slope: None,
149+
median: dist_median,
150+
median_abs_dev: dist_mad,
151+
std_dev: dist_stddev,
152+
};
153+
154+
let estimates = build_estimates(&distributions, &points, cl);
155+
156+
(distributions, estimates)
157+
}

0 commit comments

Comments
 (0)