Experimental benchmarks

This commit is contained in:
Christoph Herzog 2019-08-21 12:07:30 +02:00
parent ef3720cb67
commit acd1442cea
9 changed files with 215 additions and 137 deletions

View file

@ -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",

View file

@ -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);

View file

@ -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)
}
}

View file

@ -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)]

3
juniper_benchmarks/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

View file

@ -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"

View file

@ -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);

View file

@ -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))
}

View file

@ -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)
}