From acd1442ceac23f82911d6b3bff8337cd6106347d Mon Sep 17 00:00:00 2001 From: Christoph Herzog <chris@theduke.at> Date: Wed, 21 Aug 2019 12:07:30 +0200 Subject: [PATCH] Experimental benchmarks --- Cargo.toml | 1 + benches/bench.rs | 127 ------------------------ juniper/src/http/mod.rs | 8 +- juniper/src/lib.rs | 1 - juniper_benchmarks/.gitignore | 3 + juniper_benchmarks/Cargo.toml | 19 ++++ juniper_benchmarks/benches/benchmark.rs | 74 ++++++++++++++ juniper_benchmarks/src/lib.rs | 116 ++++++++++++++++++++++ juniper_warp/src/lib.rs | 3 +- 9 files changed, 215 insertions(+), 137 deletions(-) delete mode 100644 benches/bench.rs create mode 100644 juniper_benchmarks/.gitignore create mode 100644 juniper_benchmarks/Cargo.toml create mode 100644 juniper_benchmarks/benches/benchmark.rs create mode 100644 juniper_benchmarks/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 6f61e9f8..8a2553e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] # Order is important as this is the order the crates will be released. members = [ + "juniper_benchmarks", "juniper_codegen", "juniper", "integration_tests/juniper_tests", diff --git a/benches/bench.rs b/benches/bench.rs deleted file mode 100644 index 5d707527..00000000 --- a/benches/bench.rs +++ /dev/null @@ -1,127 +0,0 @@ -#[macro_use] extern crate bencher; -extern crate juniper; - -use bencher::Bencher; - -use juniper::{execute, RootNode, EmptyMutation, Variables}; -use juniper::tests::model::Database; - -fn query_type_name(b: &mut Bencher) { - let database = Database::new(); - let schema = RootNode::new(&database, EmptyMutation::<Database>::new()); - - let doc = r#" - query IntrospectionQueryTypeQuery { - __schema { - queryType { - name - } - } - }"#; - - b.iter(|| execute(doc, None, &schema, &Variables::new(), &database)); -} - -fn introspection_query(b: &mut Bencher) { - let database = Database::new(); - let schema = RootNode::new(&database, EmptyMutation::<Database>::new()); - - let doc = r#" - query IntrospectionQuery { - __schema { - queryType { name } - mutationType { name } - subscriptionType { name } - types { - ...FullType - } - directives { - name - description - locations - args { - ...InputValue - } - } - } - } - - fragment FullType on __Type { - kind - name - description - fields(includeDeprecated: true) { - name - description - args { - ...InputValue - } - type { - ...TypeRef - } - isDeprecated - deprecationReason - } - inputFields { - ...InputValue - } - interfaces { - ...TypeRef - } - enumValues(includeDeprecated: true) { - name - description - isDeprecated - deprecationReason - } - possibleTypes { - ...TypeRef - } - } - - fragment InputValue on __InputValue { - name - description - type { ...TypeRef } - defaultValue - } - - fragment TypeRef on __Type { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - ofType { - kind - name - } - } - } - } - } - } - } - } -"#; - - b.iter(|| execute(doc, None, &schema, &Variables::new(), &database)); -} - -benchmark_group!(queries, query_type_name, introspection_query); -benchmark_main!(queries); diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs index e6fbf21e..2d4e62bd 100644 --- a/juniper/src/http/mod.rs +++ b/juniper/src/http/mod.rs @@ -111,13 +111,7 @@ where { let op = self.operation_name(); let vars = &self.variables(); - let res = crate::execute_async( - &self.query, - op, - root_node, - vars, - context, - ).await; + let res = crate::execute_async(&self.query, op, root_node, vars, context).await; GraphQLResponse(res) } } diff --git a/juniper/src/lib.rs b/juniper/src/lib.rs index 3557560e..0d014ab2 100644 --- a/juniper/src/lib.rs +++ b/juniper/src/lib.rs @@ -91,7 +91,6 @@ Juniper has not reached 1.0 yet, thus some API instability should be expected. #![doc(html_root_url = "https://docs.rs/juniper/0.13.1")] #![warn(missing_docs)] - #![cfg_attr(feature = "async", feature(async_await, async_closure))] #[doc(hidden)] diff --git a/juniper_benchmarks/.gitignore b/juniper_benchmarks/.gitignore new file mode 100644 index 00000000..69369904 --- /dev/null +++ b/juniper_benchmarks/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/juniper_benchmarks/Cargo.toml b/juniper_benchmarks/Cargo.toml new file mode 100644 index 00000000..e4da5478 --- /dev/null +++ b/juniper_benchmarks/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "juniper_benchmarks" +version = "0.1.0" +authors = ["Christoph Herzog <chris@theduke.at>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bench]] +name = "benchmark" +harness = false + +[dependencies] +juniper = { path = "../juniper", features = ["async"] } +futures-preview = "0.3.0-alpha.18" + +[dev-dependencies] +criterion = "0.2.11" +tokio = "0.2.0-alpha.2" diff --git a/juniper_benchmarks/benches/benchmark.rs b/juniper_benchmarks/benches/benchmark.rs new file mode 100644 index 00000000..fb0646ac --- /dev/null +++ b/juniper_benchmarks/benches/benchmark.rs @@ -0,0 +1,74 @@ +extern crate juniper_benchmarks; + +use criterion::{black_box, criterion_group, criterion_main, Criterion, ParameterizedBenchmark}; + +use juniper::{graphql_value, InputValue, ToInputValue, Value}; +use juniper_benchmarks as j; + +fn bench_sync_vs_async_single_user_flat_instant(c: &mut Criterion) { + const QUERY: &'static str = r#" + query Query($id: Int) { + user(id: $id) { + id + kind + username + email + } + } + "#; + + c.bench( + "Sync vs Async - Single User Flat - Instant", + ParameterizedBenchmark::new( + "Sync", + |b, count| { + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::<Vec<_>>(); + let ids = InputValue::list(ids); + b.iter(|| { + j::execute( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ) + }) + }, + vec![1, 10], + ) + .with_function("Async - Single Thread", |b, count| { + let mut rt = tokio::runtime::current_thread::Runtime::new().unwrap(); + + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::<Vec<_>>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute_async( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }) + .with_function("Async - Threadpool", |b, count| { + let rt = tokio::runtime::Runtime::new().unwrap(); + + let ids = (0..*count) + .map(|x| InputValue::scalar(x as i32)) + .collect::<Vec<_>>(); + let ids = InputValue::list(ids); + + b.iter(|| { + let f = j::execute_async( + QUERY, + vec![("ids".to_string(), ids.clone())].into_iter().collect(), + ); + rt.block_on(f) + }) + }), + ); +} + +criterion_group!(benches, bench_sync_vs_async_single_user_flat_instant); +criterion_main!(benches); diff --git a/juniper_benchmarks/src/lib.rs b/juniper_benchmarks/src/lib.rs new file mode 100644 index 00000000..a9f2aa4b --- /dev/null +++ b/juniper_benchmarks/src/lib.rs @@ -0,0 +1,116 @@ +#![feature(async_await, async_closure)] + +use juniper::{ + object, DefaultScalarValue, ExecutionError, FieldError, GraphQLEnum, Value, Variables, +}; + +pub type QueryResult = Result< + ( + Value<DefaultScalarValue>, + Vec<ExecutionError<DefaultScalarValue>>, + ), + String, +>; + +pub struct Context {} + +impl Context { + fn new() -> Self { + Self {} + } +} + +impl juniper::Context for Context {} + +#[derive(GraphQLEnum)] +pub enum Gender { + Male, + Female, + Other, +} + +#[derive(GraphQLEnum)] +pub enum UserKind { + SuperAdmin, + Admin, + Moderator, + User, + Guest, +} + +pub struct User { + pub id: i32, + pub kind: UserKind, + pub username: String, + pub email: String, + pub gender: Option<Gender>, +} + +impl User { + fn new(id: i32) -> Self { + Self { + id, + kind: UserKind::Admin, + username: "userx".to_string(), + email: "userx@domain.com".to_string(), + gender: Some(Gender::Female), + } + } +} + +#[object(Context = Context)] +impl User {} + +pub struct Query; + +#[object(Context = Context)] +impl Query { + fn user_sync_instant(id: i32) -> Result<User, FieldError> { + Ok(User::new(id)) + } + + fn users_sync_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> { + if let Some(ids) = ids { + let users = ids.into_iter().map(User::new).collect(); + Ok(users) + } else { + Ok(vec![]) + } + } + + async fn user_async_instant(id: i32) -> Result<User, FieldError> { + Ok(User::new(id)) + } + + async fn users_async_instant(ids: Option<Vec<i32>>) -> Result<Vec<User>, FieldError> { + if let Some(ids) = ids { + let users = ids.into_iter().map(User::new).collect(); + Ok(users) + } else { + Ok(vec![]) + } + } +} + +pub struct Mutation; + +#[object(Context = Context)] +impl Mutation {} + +pub fn new_schema() -> juniper::RootNode<'static, Query, Mutation> { + juniper::RootNode::new(Query, Mutation) +} + +pub fn execute(query: &str, vars: Variables) -> QueryResult { + let root = new_schema(); + let ctx = Context::new(); + juniper::execute(query, None, &root, &vars, &ctx).map_err(|e| format!("{:?}", e)) +} + +pub async fn execute_async(query: &str, vars: Variables) -> QueryResult { + let root = new_schema(); + let ctx = Context::new(); + juniper::execute_async(query, None, &root, &vars, &ctx) + .await + .map_err(|e| format!("{:?}", e)) +} diff --git a/juniper_warp/src/lib.rs b/juniper_warp/src/lib.rs index 6f6b7711..a8725b29 100644 --- a/juniper_warp/src/lib.rs +++ b/juniper_warp/src/lib.rs @@ -39,7 +39,6 @@ Check the LICENSE file for details. #![deny(missing_docs)] #![deny(warnings)] #![doc(html_root_url = "https://docs.rs/juniper_warp/0.2.0")] - #![cfg_attr(feature = "async", feature(async_await, async_closure))] use futures::{future::poll_fn, Future}; @@ -114,7 +113,7 @@ where .iter() .map(|request| request.execute_async(root_node, context)) .collect::<Vec<_>>(); - let responses = futures03::future::join_all(futures).await; + let responses = futures03::future::join_all(futures).await; GraphQLBatchResponse::Batch(responses) }