refactor: Split benchmarks

Signed-off-by: Dmitry Dygalo <dadygalo@gmail.com>
This commit is contained in:
Dmitry Dygalo 2021-07-12 17:24:08 +02:00 committed by Dmitry Dygalo
parent 5aaa4aad92
commit 6c76d48215
20 changed files with 1411 additions and 594 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
# Rust
/jsonschema/target
/bindings/*/target
/bench_helpers/target
.hypothesis
.benchmarks
/jsonschema/Cargo.lock

39
bench_helpers/Cargo.lock generated Normal file
View File

@ -0,0 +1,39 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "bench_helpers"
version = "0.1.0"
dependencies = [
"serde_json",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]

11
bench_helpers/Cargo.toml Normal file
View File

@ -0,0 +1,11 @@
[package]
name = "bench_helpers"
version = "0.1.0"
authors = ["dmitry.dygalo <dadygalo@gmail.com>"]
edition = "2018"
license = "MIT"
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
criterion = "0.3"

108
bench_helpers/src/lib.rs Normal file
View File

@ -0,0 +1,108 @@
use criterion::Criterion;
use serde::Deserialize;
use serde_json::{from_str, Value};
use std::fs::read_to_string;
#[derive(Debug, Deserialize)]
struct Benchmark<'a> {
name: &'a str,
schema: Value,
valid: Option<Vec<Value>>,
invalid: Vec<Value>,
}
pub fn read_json(filepath: &str) -> Value {
let content = read_to_string(filepath).expect("Can't read file");
from_str(&content).expect("Invalid JSON")
}
fn strip_characters(original: &str) -> String {
original
.chars()
.filter(|&c| !"{}:\" ,[]".contains(c))
.collect()
}
pub fn bench_openapi(bench: &mut dyn FnMut(&str, Value, Value)) {
let schema = read_json("benches/data/openapi.json");
let instance = read_json("benches/data/zuora.json");
bench("openapi", schema, instance)
}
pub fn bench_swagger(bench: &mut dyn FnMut(&str, Value, Value)) {
let schema = read_json("benches/data/swagger.json");
let instance = read_json("benches/data/kubernetes.json");
bench("swagger", schema, instance)
}
pub fn bench_geojson(bench: &mut dyn FnMut(&str, Value, Value)) {
let schema = read_json("benches/data/geojson.json");
let instance = read_json("benches/data/canada.json");
bench("geojson", schema, instance)
}
pub fn bench_citm(bench: &mut dyn FnMut(&str, Value, Value)) {
let schema = read_json("benches/data/citm_catalog_schema.json");
let instance = read_json("benches/data/citm_catalog.json");
bench("CITM", schema, instance)
}
pub fn bench_fast(bench: &mut dyn FnMut(&str, Value, Value, Value)) {
let schema = read_json("benches/data/fast_schema.json");
let valid = read_json("benches/data/fast_valid.json");
let invalid = read_json("benches/data/fast_invalid.json");
bench("fast", schema, valid, invalid)
}
pub fn bench_keywords(
c: &mut Criterion,
is_skipped: &dyn Fn(&str) -> bool,
is_valid: &dyn Fn(&Value, &Value) -> bool,
bench_compile: &mut dyn FnMut(&mut Criterion, &str, &Value),
bench_valid: fn(&mut Criterion, &str, &Value, &Value),
bench_invalid: fn(&mut Criterion, &str, &Value, &Value),
) {
let content = read_to_string("benches/data/keywords.json").expect("Can't read file");
let data: Vec<Benchmark> = serde_json::from_str(&content).expect("Deserialization error");
for benchmark in data {
if is_skipped(benchmark.name) {
eprintln!("Skip {}", benchmark.name);
continue;
}
// Schema compilation
let suffix = strip_characters(&benchmark.schema.to_string());
bench_compile(
c,
&format!("{} {}", benchmark.name, suffix),
&benchmark.schema,
);
// Valid cases
if let Some(valid_cases) = benchmark.valid {
for instance in valid_cases {
if !is_valid(&benchmark.schema, &instance) {
eprintln!("This instance should be VALID: {}", benchmark.name);
}
let suffix = strip_characters(&instance.to_string());
bench_valid(
c,
&format!("{} valid {}", benchmark.name, suffix),
&benchmark.schema,
&instance,
)
}
}
// Invalid cases
for instance in benchmark.invalid {
if is_valid(&benchmark.schema, &instance) {
eprintln!("This instance should be INVALID: {}", benchmark.name);
}
let suffix = strip_characters(&instance.to_string());
bench_invalid(
c,
&format!("{} invalid {}", benchmark.name, suffix),
&benchmark.schema,
&instance,
)
}
}
}

View File

@ -40,15 +40,27 @@ criterion = ">= 0.1"
mockito = ">= 0"
json_schema_test_suite = ">= 0.3"
jsonschema-valid = "0.4.0"
bench_helpers = { path = "../bench_helpers" }
valico = "3.6.0"
test-case = "1"
paste = ">= 0.1"
reqwest = { version = ">= 0.10", features = ["blocking", "json"] }
# Benchmarks for `jsonschema`
[[bench]]
name = "jsonschema"
harness = false
# Benchmarks for `valico`
[[bench]]
name = "valico"
harness = false
# Benchmarks for `jsonschema_valid`
[[bench]]
name = "jsonschema_valid"
harness = false
[profile.release]
codegen-units = 1
lto = "on"

View File

@ -0,0 +1,932 @@
[
{
"name": "additional_items_boolean",
"schema": {
"items": [
{},
{},
{}
],
"additionalItems": false
},
"valid": [
[
1,
2,
3
]
],
"invalid": [
[
1,
2,
3,
4
]
]
},
{
"name": "additional_items_object",
"schema": {
"items": [
{},
{},
{}
],
"additionalItems": {
"type": "string"
}
},
"valid": [
[
1,
2,
3,
"foo"
]
],
"invalid": [
[
1,
2,
3,
4
]
]
},
{
"name": "additional_properties_single",
"schema": {
"additionalProperties": {
"type": "string"
}
},
"valid": [
{
"foo": "bar"
}
],
"invalid": [
{
"foo": 1
}
]
},
{
"name": "additional_properties_and_properties",
"schema": {
"additionalProperties": {
"type": "string"
},
"properties": {
"foo": {}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": 1,
"bar": true
}
]
},
{
"name": "additional_properties_and_pattern_properties",
"schema": {
"additionalProperties": {
"type": "string"
},
"patternProperties": {
"f.*o": {
"type": "integer"
}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": 1,
"bar": true
}
]
},
{
"name": "additional_properties_and_properties_and_pattern_properties",
"schema": {
"additionalProperties": {
"type": "string"
},
"properties": {
"foo": {}
},
"patternProperties": {
"f.*a": {
"type": "integer"
}
}
},
"valid": [
{
"foo": null,
"fza": 2
}
],
"invalid": [
{
"foo": null,
"fzo": 2,
"bar": true
}
]
},
{
"name": "additional_properties_false",
"schema": {
"additionalProperties": false
},
"valid": [
{}
],
"invalid": [
{
"foo": 1
}
]
},
{
"name": "additional_properties_false_and_properties",
"schema": {
"additionalProperties": false,
"properties": {
"foo": {}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": 1,
"bar": 2
}
]
},
{
"name": "additional_properties_false_and_pattern_properties",
"schema": {
"additionalProperties": false,
"patternProperties": {
"f.*o": {
"type": "integer"
}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": 1,
"bar": 2
}
]
},
{
"name": "additional_properties_false_and_properties_and_pattern_properties",
"schema": {
"additionalProperties": false,
"properties": {
"foo": {}
},
"patternProperties": {
"f.*o": {
"type": "integer"
}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": 1,
"fz0": 2,
"bar": 2
}
]
},
{
"name": "all_of",
"schema": {
"allOf": [
{
"type": "integer"
},
{
"minimum": 2
}
]
},
"valid": [
4
],
"invalid": [
1
]
},
{
"name": "any_of",
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"minimum": 2
}
]
},
"valid": [
1
],
"invalid": [
1.5
]
},
{
"name": "any_of_multiple_types",
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "string"
}
]
},
"valid": [
"foo"
],
"invalid": [
null
]
},
{
"name": "boolean_false",
"schema": false,
"invalid": [
1
]
},
{
"name": "const",
"schema": {
"const": 1
},
"valid": [
1
],
"invalid": [
"foo"
]
},
{
"name": "contains",
"schema": {
"contains": {
"minimum": 5
}
},
"valid": [
[
5
]
],
"invalid": [
[
1
]
]
},
{
"name": "enum",
"schema": {
"enum": [
1,
2,
3,
4
]
},
"valid": [
4
],
"invalid": [
5,
"6"
]
},
{
"name": "exclusive_maximum",
"schema": {
"exclusiveMaximum": 3
},
"valid": [
2
],
"invalid": [
3
]
},
{
"name": "exclusive_minimum",
"schema": {
"exclusiveMinimum": 3
},
"valid": [
4
],
"invalid": [
3
]
},
{
"name": "format_date",
"schema": {
"format": "date"
},
"valid": [
"1963-06-19"
],
"invalid": [
"06/19/1963"
]
},
{
"name": "format_datetime",
"schema": {
"format": "date-time"
},
"valid": [
"1963-06-19T08:30:06.283185Z"
],
"invalid": [
"1990-02-31T15:59:60.123-08:00"
]
},
{
"name": "format_email",
"schema": {
"format": "email"
},
"valid": [
"test@test.com"
],
"invalid": [
"foo"
]
},
{
"name": "format_hostname",
"schema": {
"format": "hostname"
},
"valid": [
"www.example.com"
],
"invalid": [
"not_a_valid_host_name"
]
},
{
"name": "format_ipv4",
"schema": {
"format": "ipv4"
},
"valid": [
"127.0.0.1"
],
"invalid": [
"127.0.0.999",
"foobar",
"2001:0db8:85a3:0000:0000:8a2e:0370:7334"
]
},
{
"name": "format_ipv6",
"schema": {
"format": "ipv6"
},
"valid": [
"2001:0db8:85a3:0000:0000:8a2e:0370:7334"
],
"invalid": [
"127.0.0.1",
"foobar"
]
},
{
"name": "format_iri",
"schema": {
"format": "iri"
},
"valid": [
"http://\u0192\u00f8\u00f8.\u00df\u00e5r/?\u2202\u00e9\u0153=\u03c0\u00eex#\u03c0\u00ee\u00fcx"
],
"invalid": [
"/abc"
]
},
{
"name": "format_iri_reference",
"schema": {
"format": "iri-reference"
},
"valid": [
"http://\u0192\u00f8\u00f8.\u00df\u00e5r/?\u2202\u00e9\u0153=\u03c0\u00eex#\u03c0\u00ee\u00fcx"
],
"invalid": [
"#\u0192r\u00e4g\\m\u00eant"
]
},
{
"name": "format_json_pointer",
"schema": {
"format": "json-pointer"
},
"valid": [
"/foo/bar~0/baz~1/%a"
],
"invalid": [
"/foo/bar~"
]
},
{
"name": "format_regex",
"schema": {
"format": "regex"
},
"valid": [
"([abc])+\\s+$"
],
"invalid": [
"^(abc]"
]
},
{
"name": "format_relative_json_pointer",
"schema": {
"format": "relative-json-pointer"
},
"valid": [
"1"
],
"invalid": [
"/foo/bar"
]
},
{
"name": "format_time",
"schema": {
"format": "time"
},
"valid": [
"08:30:06.283185Z"
],
"invalid": [
"01:01:01,1111"
]
},
{
"name": "format_uri_reference",
"schema": {
"format": "uri-reference"
},
"valid": [
"http://foo.bar/?baz=qux#quux"
],
"invalid": [
"#frag\\ment"
]
},
{
"name": "format_uri_template",
"schema": {
"format": "uri-template"
},
"valid": [
"http://example.com/dictionary/{term:1}/{term}"
],
"invalid": [
"http://example.com/dictionary/{term:1}/{term"
]
},
{
"name": "items",
"schema": {
"items": {
"type": "integer"
}
},
"valid": [
[
1,
2,
3
]
],
"invalid": [
[
1,
2,
"x"
]
]
},
{
"name": "maximum",
"schema": {
"maximum": 3
},
"valid": [
3
],
"invalid": [
5
]
},
{
"name": "max_items",
"schema": {
"maxItems": 1
},
"valid": [
[
1
]
],
"invalid": [
[
1,
2
]
]
},
{
"name": "max_length",
"schema": {
"maxLength": 3
},
"valid": [
"foo"
],
"invalid": [
"foob"
]
},
{
"name": "max_properties",
"schema": {
"maxProperties": 1
},
"valid": [
{
"a": 1
}
],
"invalid": [
{
"a": 1,
"b": 1
}
]
},
{
"name": "minimum",
"schema": {
"minimum": 3
},
"valid": [
5
],
"invalid": [
1
]
},
{
"name": "min_items",
"schema": {
"minItems": 2
},
"valid": [
[
1,
2
]
],
"invalid": [
[
1
]
]
},
{
"name": "min_length",
"schema": {
"minLength": 3
},
"valid": [
"123"
],
"invalid": [
"12"
]
},
{
"name": "min_properties",
"schema": {
"minProperties": 2
},
"valid": [
{
"a": 1,
"b": 2
}
],
"invalid": [
{
"a": 1
}
]
},
{
"name": "multiple_of_integer",
"schema": {
"multipleOf": 5
},
"valid": [
125
],
"invalid": [
212,
212.4
]
},
{
"name": "multiple_of_number",
"schema": {
"multipleOf": 2.5
},
"valid": [
127.5
],
"invalid": [
112.2
]
},
{
"name": "not",
"schema": {
"not": {
"type": "null"
}
},
"valid": [
1
],
"invalid": [
null
]
},
{
"name": "one_of",
"schema": {
"oneOf": [
{
"type": "integer"
},
{
"minimum": 2
}
]
},
"valid": [
1
],
"invalid": [
3
]
},
{
"name": "pattern",
"schema": {
"pattern": "A[0-9]{2}Z"
},
"valid": [
"A11Z"
],
"invalid": [
"A119"
]
},
{
"name": "pattern_properties",
"schema": {
"patternProperties": {
"f.*o": {
"type": "integer"
}
}
},
"valid": [
{
"foo": 1
}
],
"invalid": [
{
"foo": "bar",
"fooooo": 2
}
]
},
{
"name": "properties",
"schema": {
"properties": {
"foo": {
"type": "string"
}
}
},
"valid": [
{
"foo": "bar"
}
],
"invalid": [
{
"foo": 1
}
]
},
{
"name": "property_names",
"schema": {
"propertyNames": {
"maxLength": 3
}
},
"valid": [
{
"ABC": 1
}
],
"invalid": [
{
"ABCD": 1
}
]
},
{
"name": "ref",
"schema": {
"items": [
{
"type": "integer"
},
{
"$ref": "#/items/0"
}
]
},
"valid": [
[
1,
2
]
],
"invalid": [
[
1,
"b"
]
]
},
{
"name": "required",
"schema": {
"required": [
"a"
]
},
"valid": [
{
"a": 1
}
],
"invalid": [
{}
]
},
{
"name": "type_integer",
"schema": {
"type": "integer"
},
"valid": [
1,
1.0
],
"invalid": [
1.4,
"foo"
]
},
{
"name": "type_string",
"schema": {
"type": "string"
},
"valid": [
"foo"
],
"invalid": [
1
]
},
{
"name": "type_multiple",
"schema": {
"type": [
"integer",
"string"
]
},
"valid": [
"foo"
],
"invalid": [
[]
]
},
{
"name": "unique_items",
"schema": {
"uniqueItems": true
},
"valid": [
[
1,
"2",
[
3
],
{
"4": 4
},
5,
"6",
[
7
],
{
"8": 8
},
9,
"10",
[
11
],
{
"12": 12
}
]
],
"invalid": [
[
1,
2,
3,
4,
5,
1
]
]
}
]

View File

@ -1,624 +1,140 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use bench_helpers::{
bench_citm, bench_fast, bench_geojson, bench_keywords, bench_openapi, bench_swagger,
};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use jsonschema::JSONSchema;
use jsonschema_valid::schemas;
use serde_json::{from_str, json, Value};
use std::{fs::File, io::Read, path::Path};
use valico::json_schema;
fn read_json(filepath: &str) -> Value {
let path = Path::new(filepath);
let mut file = File::open(&path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).ok().unwrap();
let data: Value = from_str(&content).unwrap();
data
}
fn strip_characters(original: &str) -> String {
original
.chars()
.filter(|&c| !"{}:\" ,[]".contains(c))
.collect()
}
macro_rules! bench {
(
name = $name:tt;
schema = $schema:tt;
valid = $( $valid:tt ),* $(,)*;
invalid = $( $invalid:tt ),* $(,)*;
) => {
paste::item! {
#[allow(dead_code)]
fn [<bench_ $name>](c: &mut Criterion) {
let schema = json!($schema);
let validator = JSONSchema::compile(&schema).unwrap();
let suffix = strip_characters(stringify!($schema));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema).unwrap()));
$(
let instance = black_box(json!($valid));
assert!(validator.is_valid(&instance));
let suffix = strip_characters(stringify!($valid));
c.bench_function(format!("jsonschema-rs {} is_valid valid {}", $name, suffix).as_str(), |b| b.iter(|| validator.is_valid(&instance)));
c.bench_function(format!("jsonschema-rs {} validate valid {}", $name, suffix).as_str(), |b| b.iter(|| validator.validate(&instance).ok()));
)*
$(
let instance = black_box(json!($invalid));
assert!(!validator.is_valid(&instance));
let suffix = strip_characters(stringify!($invalid));
c.bench_function(format!("jsonschema-rs {} is_valid invalid {}", $name, suffix).as_str(), |b| b.iter(|| validator.is_valid(&instance)));
c.bench_function(format!("jsonschema-rs {} validate invalid {}", $name, suffix).as_str(), |b| b.iter(|| {
let _: Vec<_> = validator.validate(&instance).unwrap_err().collect();
}));
)*
}
}
};
(
name = $name:tt;
schema = $schema:tt;
invalid = $( $invalid:tt ),* $(,)*;
) => {
paste::item! {
fn [<bench_ $name>](c: &mut Criterion) {
let schema = json!($schema);
let validator = JSONSchema::compile(&schema).unwrap();
let suffix = strip_characters(stringify!($schema));
c.bench_function(format!("jsonschema-rs {} compile {}", $name, suffix).as_str(), |b| b.iter(|| JSONSchema::compile(&schema).unwrap()));
$(
let instance = black_box(json!($invalid));
assert!(!validator.is_valid(&instance));
let suffix = strip_characters(stringify!($invalid));
c.bench_function(format!("jsonschema-rs {} is_valid invalid {}", $name, suffix).as_str(), |b| b.iter(|| validator.is_valid(&instance)));
c.bench_function(format!("jsonschema-rs {} validate invalid {}", $name, suffix).as_str(), |b| b.iter(|| {
let _: Vec<_> = validator.validate(&instance).unwrap_err().collect();
}));
)*
}
}
};
}
use serde_json::Value;
macro_rules! jsonschema_rs_bench {
($c:tt, $name:expr, $schema:ident, $instance:ident) => {{
let validator = JSONSchema::options()
let compiled = JSONSchema::options()
.with_meta_schemas()
.compile(&$schema)
.expect("Invalid schema");
assert!(validator.is_valid(&$instance), "Invalid instance");
assert!(validator.validate(&$instance).is_ok(), "Invalid instance");
assert!(compiled.is_valid(&$instance), "Invalid instance");
assert!(compiled.validate(&$instance).is_ok(), "Invalid instance");
$c.bench_function(&format!("jsonschema-rs {} compile", $name), |b| {
b.iter(|| JSONSchema::options().with_meta_schemas().compile(&$schema))
});
$c.bench_function(&format!("jsonschema-rs {} is_valid", $name), |b| {
b.iter(|| validator.is_valid(&$instance))
b.iter(|| compiled.is_valid(&$instance))
});
$c.bench_function(&format!("jsonschema-rs {} validate", $name), |b| {
b.iter(|| validator.validate(&$instance).ok())
});
}};
}
macro_rules! jsonschema_valid_bench {
($c:tt, $name:expr, $schema:ident, $instance:ident, $draft:expr) => {{
let cfg =
jsonschema_valid::Config::from_schema(&$schema, Some($draft)).expect("Invalid schema");
assert!(
jsonschema_valid::validate(&cfg, &$instance).is_ok(),
"Invalid instance"
);
$c.bench_function(&format!("jsonschema-valid {}", $name), |b| {
// There is no specialized method for fast boolean return value
b.iter(|| jsonschema_valid::validate(&cfg, &$instance).is_ok())
});
}};
}
macro_rules! valico_bench {
($c:tt, $name:expr, $schema:ident, $instance:ident) => {{
let mut scope = json_schema::Scope::new();
let valico_validator = scope.compile_and_return($schema.clone(), false).unwrap();
assert!(
valico_validator.validate(&$instance).is_valid(),
"Invalid instance"
);
$c.bench_function(&format!("valico {}", $name), |b| {
// There is no specialized method for fast boolean return value
b.iter(|| valico_validator.validate(&$instance).is_valid())
b.iter(|| compiled.validate(&$instance).ok())
});
}};
}
#[allow(dead_code)]
fn large_schemas(c: &mut Criterion) {
// Open API JSON Schema
// Only `jsonschema` works correctly - other libraries do not recognize `zuora` as valid
let openapi = read_json("benches/openapi.json");
let zuora = read_json("benches/zuora.json");
jsonschema_rs_bench!(c, "openapi", openapi, zuora);
bench_openapi(&mut |name, schema, instance| jsonschema_rs_bench!(c, name, schema, instance));
// Swagger JSON Schema
let swagger = read_json("benches/swagger.json");
let kubernetes = read_json("benches/kubernetes.json");
jsonschema_rs_bench!(c, "swagger", swagger, kubernetes);
valico_bench!(c, "swagger", swagger, kubernetes);
bench_swagger(&mut |name, schema, instance| jsonschema_rs_bench!(c, name, schema, instance));
// Canada borders in GeoJSON
let geojson = read_json("benches/geojson.json");
let canada = read_json("benches/canada.json");
jsonschema_rs_bench!(c, "canada", geojson, canada);
jsonschema_valid_bench!(c, "canada", geojson, canada, schemas::Draft::Draft7);
valico_bench!(c, "canada", geojson, canada);
bench_geojson(&mut |name, schema, instance| jsonschema_rs_bench!(c, name, schema, instance));
// CITM catalog
let citm_catalog_schema = read_json("benches/citm_catalog_schema.json");
let citm_catalog = read_json("benches/citm_catalog.json");
jsonschema_rs_bench!(c, "citm_catalog", citm_catalog_schema, citm_catalog);
jsonschema_valid_bench!(
c,
"citm_catalog",
citm_catalog_schema,
citm_catalog,
schemas::Draft::Draft7
);
valico_bench!(c, "citm_catalog", citm_catalog_schema, citm_catalog);
bench_citm(&mut |name, schema, instance| jsonschema_rs_bench!(c, name, schema, instance));
}
#[allow(dead_code)]
fn fast_schema(c: &mut Criterion) {
let schema = read_json("benches/fast_schema.json");
let valid =
black_box(json!([9, "hello", [1, "a", true], {"a": "a", "b": "b", "d": "d"}, 42, 3]));
let invalid =
black_box(json!([10, "world", [1, "a", true], {"a": "a", "b": "b", "c": "xy"}, "str", 5]));
// jsonschema
let validator = JSONSchema::compile(&schema).unwrap();
assert!(validator.is_valid(&valid));
assert!(!validator.is_valid(&invalid));
c.bench_function("compare jsonschema-rs small schema compile", |b| {
b.iter(|| JSONSchema::compile(&schema).unwrap())
});
c.bench_function("compare jsonschema-rs small schema is_valid valid", |b| {
b.iter(|| validator.is_valid(&valid))
});
c.bench_function("compare jsonschema-rs small schema validate valid", |b| {
b.iter(|| validator.validate(&valid).ok())
});
c.bench_function("compare jsonschema-rs small schema is_valid invalid", |b| {
b.iter(|| validator.is_valid(&invalid))
});
c.bench_function("compare jsonschema-rs small schema validate invalid", |b| {
b.iter(|| {
let _: Vec<_> = validator.validate(&invalid).unwrap_err().collect();
})
});
// jsonschema_valid
let cfg = jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7)).unwrap();
c.bench_function("compare jsonschema_valid small schema compile", |b| {
b.iter(|| {
jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7)).unwrap()
})
});
c.bench_function(
"compare jsonschema_valid small schema validate valid",
|b| b.iter(|| jsonschema_valid::validate(&cfg, &valid)),
);
c.bench_function(
"compare jsonschema_valid small schema validate invalid",
|b| b.iter(|| jsonschema_valid::validate(&cfg, &invalid).ok()),
);
// valico
let mut scope = json_schema::Scope::new();
let compiled = scope.compile_and_return(schema.clone(), false).unwrap();
c.bench_function("compare valico small schema compile", |b| {
b.iter(|| {
let mut scope = json_schema::Scope::new();
scope.compile_and_return(schema.clone(), false).unwrap();
})
});
c.bench_function("compare valico small schema validate valid", |b| {
b.iter(|| compiled.validate(&valid).is_valid())
});
c.bench_function("compare valico small schema validate invalid", |b| {
b.iter(|| compiled.validate(&invalid).is_valid())
bench_fast(&mut |name, schema, valid, invalid| {
let compiled = JSONSchema::compile(&schema).expect("Valid schema");
assert!(compiled.is_valid(&valid));
assert!(!compiled.is_valid(&invalid));
c.bench_function(&format!("jsonschema-rs {} compile", name), |b| {
b.iter(|| JSONSchema::compile(&schema).expect("Valid schema"))
});
c.bench_function(&format!("jsonschema-rs {} is_valid valid", name), |b| {
b.iter(|| compiled.is_valid(&valid))
});
c.bench_function(&format!("jsonschema-rs {} validate valid", name), |b| {
b.iter(|| compiled.validate(&valid).ok())
});
c.bench_function(&format!("jsonschema-rs {} is_valid invalid", name), |b| {
b.iter(|| compiled.is_valid(&invalid))
});
c.bench_function(&format!("jsonschema-rs {} validate invalid", name), |b| {
b.iter(|| {
let _: Vec<_> = compiled
.validate(&invalid)
.expect_err("There should be errors")
.collect();
})
});
});
}
bench!(
name = "additional_items_boolean";
schema = {"items": [{}, {}, {}], "additionalItems": false};
valid = [1, 2, 3];
invalid = [1, 2, 3, 4];
);
bench!(
name = "additional_items_object";
schema = {"items": [{}, {}, {}], "additionalItems": {"type": "string"}};
valid = [1, 2, 3, "foo"];
invalid = [1, 2, 3, 4];
);
bench!(
name = "additional_properties_single";
schema = {"additionalProperties": {"type": "string"}};
valid = {"foo": "bar"};
invalid = {"foo": 1};
);
bench!(
name = "additional_properties_and_properties";
schema = {"additionalProperties": {"type": "string"}, "properties": {"foo": {}}};
valid = {"foo": 1};
invalid = {"foo": 1, "bar": true};
);
bench!(
name = "additional_properties_and_pattern_properties";
schema = {"additionalProperties": {"type": "string"}, "patternProperties": {"f.*o": {"type": "integer"}}};
valid = {"foo": 1};
invalid = {"foo": 1, "bar": true};
);
bench!(
name = "additional_properties_and_properties_and_pattern_properties";
schema = {"additionalProperties": {"type": "string"}, "properties": {"foo": {}}, "patternProperties": {"f.*a": {"type": "integer"}}};
valid = {"foo": null, "fza": 2};
invalid = {"foo": null, "fzo": 2, "bar": true};
);
bench!(
name = "additional_properties_false";
schema = {"additionalProperties": false};
valid = {};
invalid = {"foo": 1};
);
bench!(
name = "additional_properties_false_and_properties";
schema = {"additionalProperties": false, "properties": {"foo": {}}};
valid = {"foo": 1};
invalid = {"foo": 1, "bar": 2};
);
bench!(
name = "additional_properties_false_and_pattern_properties";
schema = {"additionalProperties": false, "patternProperties": {"f.*o": {"type": "integer"}}};
valid = {"foo": 1};
invalid = {"foo": 1, "bar": 2};
);
bench!(
name = "additional_properties_false_and_properties_and_pattern_properties";
schema = {"additionalProperties": false, "properties": {"foo": {}}, "patternProperties": {"f.*o": {"type": "integer"}}};
valid = {"foo": 1};
invalid = {"foo": 1, "fz0": 2, "bar": 2};
);
bench!(
name = "all_of";
schema = {"allOf": [{"type": "integer"}, {"minimum": 2}]};
valid = 4;
invalid = 1;
);
bench!(
name = "any_of";
schema = {"anyOf": [{"type": "integer"}, {"minimum": 2}]};
valid = 1;
invalid = 1.5;
);
bench!(
name = "any_of_multiple_types";
schema = {"anyOf": [{"type": "integer"}, {"type": "string"}]};
valid = "foo";
invalid = null;
);
bench!(
name = "boolean_false";
schema = false;
invalid = 1;
);
bench!(
name = "const";
schema = {"const": 1};
valid = 1;
invalid = "foo";
);
bench!(
name = "contains";
schema = {"contains": {"minimum": 5}};
valid = [5];
invalid = [1];
);
bench!(
name = "enum";
schema = {"enum": [1, 2, 3, 4]};
valid = 4;
invalid = 5, "6";
);
bench!(
name = "exclusive_maximum";
schema = {"exclusiveMaximum": 3};
valid = 2;
invalid = 3;
);
bench!(
name = "exclusive_minimum";
schema = {"exclusiveMinimum": 3};
valid = 4;
invalid = 3;
);
bench!(
name = "format_date";
schema = {"format": "date"};
valid = "1963-06-19";
invalid = "06/19/1963";
);
bench!(
name = "format_datetime";
schema = {"format": "date-time"};
valid = "1963-06-19T08:30:06.283185Z";
invalid = "1990-02-31T15:59:60.123-08:00";
);
bench!(
name = "format_email";
schema = {"format": "email"};
valid = "test@test.com";
invalid = "foo";
);
bench!(
name = "format_hostname";
schema = {"format": "hostname"};
valid = "www.example.com";
invalid = "not_a_valid_host_name";
);
bench!(
name = "format_ipv4";
schema = {"format": "ipv4"};
valid = "127.0.0.1";
invalid = "127.0.0.999", "foobar", "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
);
bench!(
name = "format_ipv6";
schema = {"format": "ipv6"};
valid = "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
invalid = "127.0.0.1", "foobar";
);
bench!(
name = "format_iri";
schema = {"format": "iri"};
valid = "http://ƒøø.ßår/?∂éœ=πîx#πîüx";
invalid = "/abc";
);
bench!(
name = "format_iri_reference";
schema = {"format": "iri-reference"};
valid = "http://ƒøø.ßår/?∂éœ=πîx#πîüx";
invalid = "#ƒräg\\mênt";
);
bench!(
name = "format_json_pointer";
schema = {"format": "json-pointer"};
valid = "/foo/bar~0/baz~1/%a";
invalid = "/foo/bar~";
);
bench!(
name = "format_regex";
schema = {"format": "regex"};
valid = r#"([abc])+\s+$"#;
invalid = "^(abc]";
);
bench!(
name = "format_relative_json_pointer";
schema = {"format": "relative-json-pointer"};
valid = "1";
invalid = "/foo/bar";
);
bench!(
name = "format_time";
schema = {"format": "time"};
valid = "08:30:06.283185Z";
invalid = "01:01:01,1111";
);
bench!(
name = "format_uri_reference";
schema = {"format": "uri-reference"};
valid = "http://foo.bar/?baz=qux#quux";
invalid = "#frag\\ment";
);
bench!(
name = "format_uri_template";
schema = {"format": "uri-template"};
valid = "http://example.com/dictionary/{term:1}/{term}";
invalid = "http://example.com/dictionary/{term:1}/{term";
);
bench!(
name = "items";
schema = {"items": {"type": "integer"}};
valid = [1, 2, 3];
invalid = [1, 2, "x"];
);
bench!(
name = "maximum";
schema = {"maximum": 3};
valid = 3;
invalid = 5;
);
bench!(
name = "max_items";
schema = {"maxItems": 1};
valid = [1];
invalid = [1, 2];
);
bench!(
name = "max_length";
schema = {"maxLength": 3};
valid = "foo";
invalid = "foob";
);
bench!(
name = "max_properties";
schema = {"maxProperties": 1};
valid = {"a": 1};
invalid = {"a": 1, "b": 1};
);
bench!(
name = "minimum";
schema = {"minimum": 3};
valid = 5;
invalid = 1;
);
bench!(
name = "min_items";
schema = {"minItems": 2};
valid = [1, 2];
invalid = [1];
);
bench!(
name = "min_length";
schema = {"minLength": 3};
valid = "123";
invalid = "12";
);
bench!(
name = "min_properties";
schema = {"minProperties": 2};
valid = {"a": 1, "b": 2};
invalid = {"a": 1};
);
bench!(
name = "multiple_of_integer";
schema = {"multipleOf": 5};
valid = 125;
invalid = 212, 212.4;
);
bench!(
name = "multiple_of_number";
schema = {"multipleOf": 2.5};
valid = 127.5;
invalid = 112.2;
);
bench!(
name = "not";
schema = {"not": {"type": "null"}};
valid = 1;
invalid = null;
);
bench!(
name = "one_of";
schema = {"oneOf": [{"type": "integer"}, {"minimum": 2}]};
valid = 1;
invalid = 3;
);
bench!(
name = "pattern";
schema = {"pattern": "A[0-9]{2}Z"};
valid = "A11Z";
invalid = "A119";
);
bench!(
name = "pattern_properties";
schema = {"patternProperties": {"f.*o": {"type": "integer"}}};
valid = {"foo": 1};
invalid = {"foo": "bar", "fooooo": 2};
);
bench!(
name = "properties";
schema = {"properties": {"foo": {"type": "string"}}};
valid = {"foo": "bar"};
invalid = {"foo": 1};
);
bench!(
name = "property_names";
schema = {"propertyNames": {"maxLength": 3}};
valid = {"ABC": 1};
invalid = {"ABCD": 1};
);
bench!(
name = "ref";
schema = {"items": [{"type": "integer"},{"$ref": "#/items/0"}]};
valid = [1, 2];
invalid = [1, "b"];
);
bench!(
name = "required";
schema = {"required": ["a"]};
valid = {"a": 1};
invalid = {};
);
bench!(
name = "type_integer";
schema = {"type": "integer"};
valid = 1, 1.0;
invalid = 1.4, "foo";
);
bench!(
name = "type_string";
schema = {"type": "string"};
valid = "foo";
invalid = 1;
);
bench!(
name = "type_multiple";
schema = {"type": ["integer", "string"]};
valid = "foo";
invalid = [];
);
bench!(
name = "unique_items";
schema = {"uniqueItems": true};
valid = [1, "2", [3], {"4": 4}, 5, "6", [7], {"8": 8}, 9, "10", [11], {"12": 12}];
invalid = [1, 2, 3, 4, 5, 1];
);
fn keywords(c: &mut Criterion) {
bench_keywords(
c,
&|_: &str| false,
&|schema: &Value, instance: &Value| {
let compiled = JSONSchema::compile(schema).expect("Valid schema");
compiled.is_valid(instance)
},
&mut |c: &mut Criterion, name: &str, schema: &Value| {
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_rs/compile"),
schema,
|b, schema| {
b.iter(|| {
JSONSchema::compile(schema).expect("Valid schema");
})
},
);
},
validate_valid,
validate_invalid,
)
}
criterion_group!(
keywords,
bench_additional_items_boolean,
bench_additional_items_object,
bench_additional_properties_single,
bench_additional_properties_and_properties,
bench_additional_properties_and_pattern_properties,
bench_additional_properties_and_properties_and_pattern_properties,
bench_additional_properties_false,
bench_additional_properties_false_and_properties,
bench_additional_properties_false_and_pattern_properties,
bench_additional_properties_false_and_properties_and_pattern_properties,
bench_all_of,
bench_any_of,
bench_any_of_multiple_types,
bench_boolean_false,
bench_const,
bench_contains,
bench_enum,
bench_exclusive_maximum,
bench_exclusive_minimum,
bench_format_date,
bench_format_datetime,
bench_format_email,
bench_format_hostname,
bench_format_ipv4,
bench_format_ipv6,
bench_format_iri,
bench_format_iri_reference,
bench_format_json_pointer,
bench_format_regex,
bench_format_relative_json_pointer,
bench_format_time,
bench_format_uri_reference,
bench_format_uri_template,
bench_items,
bench_maximum,
bench_max_items,
bench_max_length,
bench_max_properties,
bench_minimum,
bench_min_items,
bench_min_length,
bench_min_properties,
bench_multiple_of_integer,
bench_multiple_of_number,
bench_not,
bench_one_of,
bench_pattern,
bench_pattern_properties,
bench_properties,
bench_property_names,
bench_ref,
bench_required,
bench_type_integer,
bench_type_string,
bench_type_multiple,
bench_unique_items,
);
criterion_group!(arbitrary, large_schemas, fast_schema);
criterion_main!(arbitrary, keywords);
fn validate_valid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(&schema).expect("Valid schema");
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_rs/is_valid"),
instance,
|b, instance| {
b.iter(|| {
let _ = compiled.is_valid(instance);
})
},
);
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_rs/validate"),
instance,
|b, instance| {
b.iter(|| {
compiled.validate(instance).ok();
})
},
);
}
fn validate_invalid(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let compiled = JSONSchema::compile(&schema).expect("Valid schema");
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_rs/is_valid"),
instance,
|b, instance| {
b.iter(|| {
let _ = compiled.is_valid(instance);
})
},
);
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_rs/validate"),
instance,
|b, instance| {
b.iter(|| {
let _: Vec<_> = compiled
.validate(&instance)
.expect_err("There should be errors")
.collect();
})
},
);
}
criterion_group!(arbitrary, large_schemas, fast_schema, keywords);
criterion_main!(arbitrary);

View File

@ -0,0 +1,100 @@
use bench_helpers::{bench_citm, bench_fast, bench_geojson, bench_keywords};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use jsonschema_valid::schemas;
use serde_json::Value;
macro_rules! jsonschema_valid_bench {
($c:tt, $name:expr, $schema:ident, $instance:ident, $draft:expr) => {{
let cfg =
jsonschema_valid::Config::from_schema(&$schema, Some($draft)).expect("Invalid schema");
assert!(
jsonschema_valid::validate(&cfg, &$instance).is_ok(),
"Invalid instance"
);
$c.bench_function(&format!("jsonschema-valid {}", $name), |b| {
// There is no specialized method for fast boolean return value
b.iter(|| jsonschema_valid::validate(&cfg, &$instance).is_ok())
});
}};
}
fn large_schemas(c: &mut Criterion) {
// Canada borders in GeoJSON
bench_geojson(&mut |name, schema, instance| {
jsonschema_valid_bench!(c, name, schema, instance, schemas::Draft::Draft7)
});
// CITM catalog
bench_citm(&mut |name, schema, instance| {
jsonschema_valid_bench!(c, name, schema, instance, schemas::Draft::Draft7)
});
}
fn fast_schema(c: &mut Criterion) {
bench_fast(&mut |name, schema, valid, invalid| {
let cfg = jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7))
.expect("Valid schema");
c.bench_function(&format!("jsonschema_valid {} compile", name), |b| {
b.iter(|| {
jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7))
.expect("Valid schema")
})
});
c.bench_function(&format!("jsonschema_valid {} validate valid", name), |b| {
b.iter(|| jsonschema_valid::validate(&cfg, &valid))
});
c.bench_function(
&format!("jsonschema_valid {} validate invalid", name),
|b| b.iter(|| jsonschema_valid::validate(&cfg, &invalid).ok()),
);
});
}
fn keywords(c: &mut Criterion) {
bench_keywords(
c,
&|name: &str| {
// Bug in `jsonschema_valid`
// `Option::unwrap()` on a `None` value'
// https://github.com/mdboom/jsonschema-valid/blob/de1da64fb624085eccde290e036a2ed592656f38/src/validators.rs#L531
name == "multiple_of_integer"
},
&|schema: &Value, instance: &Value| {
let compiled =
jsonschema_valid::Config::from_schema(schema, Some(schemas::Draft::Draft7))
.expect("Valid schema");
let result = jsonschema_valid::validate(&compiled, instance).is_ok();
result
},
&mut |c: &mut Criterion, name: &str, schema: &Value| {
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_valid/compile"),
schema,
|b, schema| {
b.iter(|| {
jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7))
.expect("Valid schema")
})
},
);
},
validate,
validate,
)
}
fn validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let compiled = jsonschema_valid::Config::from_schema(&schema, Some(schemas::Draft::Draft7))
.expect("Valid schema");
c.bench_with_input(
BenchmarkId::new(name, "jsonschema_valid"),
&(compiled, instance),
|b, (compiled, instance)| {
b.iter(|| {
let _ = jsonschema_valid::validate(compiled, instance);
})
},
);
}
criterion_group!(jsonschema_valid, large_schemas, fast_schema, keywords);
criterion_main!(jsonschema_valid);

View File

@ -0,0 +1,98 @@
use bench_helpers::{bench_citm, bench_fast, bench_geojson, bench_keywords, bench_swagger};
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use serde_json::Value;
use valico::json_schema;
macro_rules! valico_bench {
($c:tt, $name:expr, $schema:ident, $instance:ident) => {{
let mut scope = json_schema::Scope::new();
let compiled = scope
.compile_and_return($schema.clone(), false)
.expect("Valid schema");
assert!(compiled.validate(&$instance).is_valid(), "Invalid instance");
$c.bench_function(&format!("valico {}", $name), |b| {
// There is no specialized method for fast boolean return value
b.iter(|| compiled.validate(&$instance).is_valid())
});
}};
}
fn large_schemas(c: &mut Criterion) {
// Swagger JSON Schema
bench_swagger(&mut |name, schema, instance| valico_bench!(c, name, schema, instance));
// Canada borders in GeoJSON
bench_geojson(&mut |name, schema, instance| valico_bench!(c, name, schema, instance));
// CITM catalog
bench_citm(&mut |name, schema, instance| valico_bench!(c, name, schema, instance));
}
fn fast_schema(c: &mut Criterion) {
bench_fast(&mut |name, schema, valid, invalid| {
let mut scope = json_schema::Scope::new();
let compiled = scope
.compile_and_return(schema.clone(), false)
.expect("Valid schema");
c.bench_function(&format!("valico {} compile", name), |b| {
b.iter(|| {
let mut scope = json_schema::Scope::new();
scope
.compile_and_return(schema.clone(), false)
.expect("Valid schema");
})
});
c.bench_function(&format!("valico {} validate valid", name), |b| {
b.iter(|| compiled.validate(&valid).is_valid())
});
c.bench_function(&format!("valico {} validate invalid", name), |b| {
b.iter(|| compiled.validate(&invalid).is_valid())
});
});
}
fn keywords(c: &mut Criterion) {
bench_keywords(
c,
&|_: &str| false,
&|schema: &Value, instance: &Value| {
let mut scope = json_schema::Scope::new();
let compiled = scope
.compile_and_return(schema.clone(), false)
.expect("Valid schema");
compiled.validate(instance).is_valid()
},
&mut |c: &mut Criterion, name: &str, schema: &Value| {
c.bench_with_input(
BenchmarkId::new(name, "valico/compile"),
schema,
|b, schema| {
b.iter_with_setup(
|| schema.clone(),
|schema| {
let mut scope = json_schema::Scope::new();
scope
.compile_and_return(schema, false)
.expect("Valid schema");
},
)
},
);
},
validate,
validate,
)
}
fn validate(c: &mut Criterion, name: &str, schema: &Value, instance: &Value) {
let mut scope = json_schema::Scope::new();
let compiled = scope
.compile_and_return(schema.clone(), false)
.expect("Valid schema");
c.bench_with_input(BenchmarkId::new(name, "valico"), instance, |b, instance| {
b.iter(|| {
compiled.validate(instance).is_valid();
})
});
}
criterion_group!(valico, large_schemas, fast_schema, keywords);
criterion_main!(valico);