From 2406508b26eb5654f9da8d5513928aacbcdd5b44 Mon Sep 17 00:00:00 2001 From: Maksym Vorobiov Date: Fri, 21 Feb 2020 02:03:17 +0300 Subject: [PATCH] add benchmark comparing unsafecell vs refcell --- actix-service/Cargo.toml | 5 + .../benches/unsafecell_vs_refcell.rs | 115 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 actix-service/benches/unsafecell_vs_refcell.rs diff --git a/actix-service/Cargo.toml b/actix-service/Cargo.toml index e4e3e0b8..24640689 100644 --- a/actix-service/Cargo.toml +++ b/actix-service/Cargo.toml @@ -21,3 +21,8 @@ pin-project = "0.4.6" [dev-dependencies] actix-rt = "1.0.0" +criterion = "0.3" + +[[bench]] +name = "unsafecell_vs_refcell" +harness = false diff --git a/actix-service/benches/unsafecell_vs_refcell.rs b/actix-service/benches/unsafecell_vs_refcell.rs new file mode 100644 index 00000000..f428829b --- /dev/null +++ b/actix-service/benches/unsafecell_vs_refcell.rs @@ -0,0 +1,115 @@ +use criterion::{criterion_main, Criterion}; +use futures_util::future::join_all; +use std::cell::{RefCell, UnsafeCell}; +use std::task::{Context, Poll}; +use std::rc::Rc; +use actix_service::{Service}; +use futures_util::future::{ok, Ready}; + +struct SrvUC(Rc>); + +impl Default for SrvUC { + fn default() -> Self { + Self(Rc::new(UnsafeCell::new(0))) + } +} + +impl Clone for SrvUC { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for SrvUC { + type Request = (); + type Response = usize; + type Error = (); + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, _: ()) -> Self::Future { + unsafe { *(*self.0).get() = *(*self.0).get() + 1 }; + ok(unsafe { *self.0.get() }) + } +} + +struct SrvRC(Rc>); + +impl Default for SrvRC { + fn default() -> Self { + Self(Rc::new(RefCell::new(0))) + } +} + +impl Clone for SrvRC { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for SrvRC { + type Request = (); + type Response = usize; + type Error = (); + type Future = Ready>; + + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: ()) -> Self::Future { + let prev = *self.0.borrow(); + *(*self.0).borrow_mut() = prev + 1; + ok(*self.0.borrow()) + } +} + + +/// Criterion Benchmark for async Service +/// Should be used from within criterion group: +/// ```rust,ignore +/// let mut criterion: ::criterion::Criterion<_> = +/// ::criterion::Criterion::default().configure_from_args(); +/// bench_async_service(&mut criterion, ok_service(), "async_service_direct"); +/// ``` +/// +/// Usable for benching Service wrappers: +/// Using minimum service code implementation we first measure +/// time to run minimum service, then measure time with wrapper. +/// +/// Sample output +/// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] +pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) +where + S: Service + Clone + 'static, +{ + let mut rt = actix_rt::System::new("test"); + + // start benchmark loops + c.bench_function(name, move |b| { + b.iter_custom(|iters| { + let mut srvs: Vec<_> = (1..iters).map(|_| srv.clone()).collect(); + // exclude request generation, it appears it takes significant time vs call (3us vs 1us) + let start = std::time::Instant::now(); + // benchmark body + rt.block_on(async move { + join_all(srvs.iter_mut().map(|srv| srv.call(()))).await + }); + let elapsed = start.elapsed(); + // check that at least first request succeeded + elapsed + }) + }); +} + + +pub fn service_benches() { + let mut criterion: ::criterion::Criterion<_> = + ::criterion::Criterion::default().configure_from_args(); + bench_async_service(&mut criterion, SrvUC::default(), "Service with UnsafeCell"); + bench_async_service(&mut criterion, SrvRC::default(), "Service with RefCell"); +} +criterion_main!(service_benches);