use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use serde::{Deserialize, Serialize};
use std::{
future::Future,
hint::black_box,
pin::{Pin, pin},
task::{Context, Poll, Waker},
time::Duration,
};
use es_entity::{ContextData, EntityEvents, EventContext, WithEventContext, *};
es_entity::entity_id! { BenchEntityId }
#[derive(EsEvent, Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[es_event(id = "BenchEntityId", event_context)]
enum BenchEvent {
Happened { value: u64 },
}
const KEYS: [&str; 8] = [
"key_0", "key_1", "key_2", "key_3", "key_4", "key_5", "key_6", "key_7",
];
fn context_data(entries: usize) -> ContextData {
let mut ctx = EventContext::fork();
for key in KEYS.iter().take(entries) {
ctx.insert(key, &"01996a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b")
.unwrap();
}
ctx.data()
}
const POLLS: u32 = 64;
struct YieldN(u32);
impl Future for YieldN {
type Output = ();
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> {
if self.0 == 0 {
Poll::Ready(())
} else {
self.0 -= 1;
Poll::Pending
}
}
}
fn drive<F: Future>(fut: F) -> F::Output {
let mut fut = pin!(fut);
let mut cx = Context::from_waker(Waker::noop());
loop {
if let Poll::Ready(out) = fut.as_mut().poll(&mut cx) {
return out;
}
}
}
fn per_poll_overhead(c: &mut Criterion) {
let mut g = c.benchmark_group("1. per_poll_overhead");
g.throughput(Throughput::Elements(POLLS as u64 + 1));
g.bench_function("bare_future", |b| {
b.iter(|| drive(black_box(YieldN(POLLS))))
});
for entries in [0usize, 3] {
let data = context_data(entries);
g.bench_function(
BenchmarkId::new("with_event_context", format!("{entries}_entries")),
|b| b.iter(|| drive(black_box(YieldN(POLLS)).with_event_context(data.clone()))),
);
}
g.finish();
}
async fn yield_many() {
for _ in 0..POLLS {
tokio::task::yield_now().await;
}
}
fn tokio_yield(c: &mut Criterion) {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.enable_all()
.build()
.unwrap();
let mut g = c.benchmark_group("2. tokio_yield");
g.throughput(Throughput::Elements(POLLS as u64 + 1));
g.bench_function("bare_future", |b| b.to_async(&rt).iter(yield_many));
let data = context_data(3);
g.bench_function("with_event_context/3_entries", |b| {
b.to_async(&rt)
.iter(|| yield_many().with_event_context(data.clone()))
});
g.finish();
}
fn context_data_clone(c: &mut Criterion) {
let mut g = c.benchmark_group("3. context_data_clone");
for entries in [0usize, 3, 8] {
let data = context_data(entries);
g.bench_function(
BenchmarkId::from_parameter(format!("{entries}_entries")),
|b| b.iter(|| black_box(data.clone())),
);
}
g.finish();
}
fn context_lifecycle(c: &mut Criterion) {
let mut g = c.benchmark_group("4. context_lifecycle");
let data = context_data(3);
g.bench_function("seed_drop/3_entries", |b| {
b.iter(|| {
let ctx = EventContext::seed(black_box(data.clone()));
drop(ctx);
})
});
g.bench_function("seed_insert_drop/3_entries", |b| {
b.iter(|| {
let mut ctx = EventContext::seed(black_box(data.clone()));
ctx.insert("inserted_key", &"inserted-value").unwrap();
drop(ctx);
})
});
g.finish();
}
fn persist_path(c: &mut Criterion) {
let mut g = c.benchmark_group("5. persist_path");
let mut ambient = EventContext::current();
for key in KEYS.iter().take(3) {
ambient
.insert(key, &"01996a2b-3c4d-5e6f-7a8b-9c0d1e2f3a4b")
.unwrap();
}
g.bench_function("entity_events_push/3_ambient_entries", |b| {
b.iter_batched(
|| EntityEvents::<BenchEvent>::init(BenchEntityId::new(), std::iter::empty()),
|mut events| {
events.push(BenchEvent::Happened { value: 42 });
events
},
BatchSize::SmallInput,
)
});
let data = context_data(3);
g.bench_function("serde_to_value/3_entries", |b| {
b.iter(|| serde_json::to_value(black_box(&data)).unwrap())
});
g.finish();
drop(ambient);
}
criterion_group!(
name = benches;
config = Criterion::default()
.warm_up_time(Duration::from_millis(500))
.measurement_time(Duration::from_secs(2));
targets = per_poll_overhead, tokio_yield, context_data_clone, context_lifecycle, persist_path
);
criterion_main!(benches);