diff --git a/Cargo.toml b/Cargo.toml index 9f0748e0c..afebfc486 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -125,3 +125,7 @@ harness = false [[bench]] name = "service" harness = false + +[[bench]] +name = "cloneable" +harness = false diff --git a/benches/cloneable.rs b/benches/cloneable.rs new file mode 100644 index 000000000..31993eeaf --- /dev/null +++ b/benches/cloneable.rs @@ -0,0 +1,37 @@ +mod experiments; +mod service; + +use actix_web::test::ok_service; +use criterion::{criterion_main, Criterion}; +use experiments::cloneable::cloneable; +use experiments::cloneable::cloneable_safe; +use service::bench_async_service; + +// This is benchmark of effect by replacing UnsafeCell to RefCell in CloneableService +// Issue: https://github.com/actix/actix-web/issues/1295 +// +// Note: numbers may vary from run to run +-20%, probably due to async env +// async_service_direct time: [1.0076 us 1.0300 us 1.0507 us] +// change: [-32.491% -23.295% -15.790%] (p = 0.00 < 0.05) +// async_service_cloneable_unsafe +// time: [1.0857 us 1.1208 us 1.1629 us] +// change: [-2.9318% +5.7660% +15.004%] (p = 0.27 > 0.05) +// async_service_cloneable_safe +// time: [1.0703 us 1.1002 us 1.1390 us] +// change: [-9.2951% -1.1186% +6.5384%] (p = 0.80 > 0.05) + +pub fn service_benches() { + let mut criterion: Criterion<_> = Criterion::default().configure_from_args(); + bench_async_service(&mut criterion, ok_service(), "async_service_direct"); + bench_async_service( + &mut criterion, + cloneable::CloneableService::new(ok_service()), + "async_service_cloneable_unsafe", + ); + bench_async_service( + &mut criterion, + cloneable_safe::CloneableService::new(ok_service()), + "async_service_cloneable_safe", + ); +} +criterion_main!(service_benches); diff --git a/benches/experiments/cloneable/cloneable.rs b/benches/experiments/cloneable/cloneable.rs new file mode 100644 index 000000000..65c6bec21 --- /dev/null +++ b/benches/experiments/cloneable/cloneable.rs @@ -0,0 +1,36 @@ +use std::cell::UnsafeCell; +use std::rc::Rc; +use std::task::{Context, Poll}; + +use actix_service::Service; + +#[doc(hidden)] +/// Service that allows to turn non-clone service to a service with `Clone` impl +pub(crate) struct CloneableService(Rc>); + +impl CloneableService { + pub(crate) fn new(service: T) -> Self { + Self(Rc::new(UnsafeCell::new(service))) + } +} + +impl Clone for CloneableService { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for CloneableService { + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Future = T::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + unsafe { &mut *self.0.as_ref().get() }.poll_ready(cx) + } + + fn call(&mut self, req: T::Request) -> Self::Future { + unsafe { &mut *self.0.as_ref().get() }.call(req) + } +} diff --git a/benches/experiments/cloneable/cloneable_safe.rs b/benches/experiments/cloneable/cloneable_safe.rs new file mode 100644 index 000000000..0540b59cd --- /dev/null +++ b/benches/experiments/cloneable/cloneable_safe.rs @@ -0,0 +1,36 @@ +use std::cell::RefCell; +use std::rc::Rc; +use std::task::{Context, Poll}; + +use actix_service::Service; + +#[doc(hidden)] +/// Service that allows to turn non-clone service to a service with `Clone` impl +pub(crate) struct CloneableService(Rc>); + +impl CloneableService { + pub(crate) fn new(service: T) -> Self { + Self(Rc::new(RefCell::new(service))) + } +} + +impl Clone for CloneableService { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for CloneableService { + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Future = T::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.0.borrow_mut().poll_ready(cx) + } + + fn call(&mut self, req: T::Request) -> Self::Future { + self.0.borrow_mut().call(req) + } +} diff --git a/benches/experiments/cloneable/mod.rs b/benches/experiments/cloneable/mod.rs new file mode 100644 index 000000000..531b034c7 --- /dev/null +++ b/benches/experiments/cloneable/mod.rs @@ -0,0 +1,2 @@ +pub mod cloneable; +pub mod cloneable_safe; diff --git a/benches/experiments/mod.rs b/benches/experiments/mod.rs new file mode 100644 index 000000000..a239c5498 --- /dev/null +++ b/benches/experiments/mod.rs @@ -0,0 +1 @@ +pub mod cloneable; diff --git a/benches/service.rs b/benches/service.rs index 2fb4ca03d..8adbc8a0c 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -1,22 +1,33 @@ use actix_service::Service; use actix_web::dev::{ServiceRequest, ServiceResponse}; -use actix_web::{test, web, App, Error, HttpResponse}; -use criterion::{criterion_group, criterion_main, Criterion}; +use actix_web::{web, App, Error, HttpResponse}; +use criterion::{criterion_main, Criterion}; use std::cell::RefCell; use std::rc::Rc; -use crate::test::{init_service, ok_service, TestRequest}; +use actix_web::test::{init_service, ok_service, TestRequest}; -// TOOD: probably convert to macro? - -// Following approach is usable for benching Service wrappers -// Using minimum service code implementation we first measure -// time to run minimum service, then measure time with wrapper. -// Sample results on MacBook Pro '14 -// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] -fn async_cloneable_wrapper_service(c: &mut Criterion) { +/// 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 + + 'static, +{ let mut rt = actix_rt::System::new("test"); - let srv = Rc::new(RefCell::new(ok_service())); + let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); assert!(rt @@ -26,7 +37,7 @@ fn async_cloneable_wrapper_service(c: &mut Criterion) { .is_success()); // start benchmark loops - c.bench_function("async_service_direct", move |b| { + c.bench_function(name, move |b| { b.iter_custom(|iters| { let srv = srv.clone(); // exclude request generation, it appears it takes significant time vs call (3us vs 1us) @@ -55,7 +66,7 @@ async fn index(req: ServiceRequest) -> Result { // this approach is usable for benching WebService, though it adds some time to direct service call: // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] -fn async_cloneable_wrapper_web_service(c: &mut Criterion) { +fn async_web_service(c: &mut Criterion) { let mut rt = actix_rt::System::new("test"); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), @@ -88,9 +99,10 @@ fn async_cloneable_wrapper_web_service(c: &mut Criterion) { }); } -criterion_group!( - service_benches, - async_cloneable_wrapper_service, - async_cloneable_wrapper_web_service -); +pub fn service_benches() { + let mut criterion: ::criterion::Criterion<_> = + ::criterion::Criterion::default().configure_from_args(); + bench_async_service(&mut criterion, ok_service(), "async_service_direct"); + async_web_service(&mut criterion); +} criterion_main!(service_benches);