use std::{error::Error, hint::black_box};
use criterion::{Criterion, criterion_group, criterion_main};
use serde::{Deserializer, de::Visitor};
use serde_json::de::{IoRead, Read, StrRead};
use struson::reader::*;
fn call_unwrap<F: FnOnce() -> Result<(), Box<dyn Error>>>(f: F) {
f().unwrap();
}
fn bench_compare(c: &mut Criterion, name: &str, json: &str, include_no_path_tracking: bool) {
let mut group = c.benchmark_group(name);
group.bench_with_input("struson-skip", json, |b, json| {
b.iter(|| {
call_unwrap(|| {
let mut json_reader = JsonStreamReader::new_custom(
json.as_bytes(),
ReaderSettings {
max_nesting_depth: None,
..Default::default()
},
);
json_reader.skip_value()?;
json_reader.consume_trailing_whitespace()?;
Ok(())
});
})
});
if include_no_path_tracking {
group.bench_with_input("struson-skip (no path tracking)", json, |b, json| {
b.iter(|| {
call_unwrap(|| {
let mut json_reader = JsonStreamReader::new_custom(
json.as_bytes(),
ReaderSettings {
track_path: false,
max_nesting_depth: None,
..Default::default()
},
);
json_reader.skip_value()?;
json_reader.consume_trailing_whitespace()?;
Ok(())
});
})
});
}
fn struson_read<R: std::io::Read>(
mut json_reader: JsonStreamReader<R>,
) -> Result<(), Box<dyn Error>> {
enum StackValue {
Array,
Object,
}
let mut stack = Vec::new();
loop {
if !stack.is_empty() {
match stack.last().unwrap() {
StackValue::Array => {
if !json_reader.has_next()? {
stack.pop();
json_reader.end_array()?;
if stack.is_empty() {
break;
} else {
continue;
}
}
}
StackValue::Object => {
if json_reader.has_next()? {
black_box(json_reader.next_name()?);
} else {
stack.pop();
json_reader.end_object()?;
if stack.is_empty() {
break;
} else {
continue;
}
}
}
}
}
match json_reader.peek()? {
ValueType::Array => {
json_reader.begin_array()?;
stack.push(StackValue::Array)
}
ValueType::Object => {
json_reader.begin_object()?;
stack.push(StackValue::Object)
}
ValueType::String => {
black_box(json_reader.next_str()?);
}
ValueType::Number => {
black_box(json_reader.next_number_as_str()?);
}
ValueType::Boolean => {
black_box(json_reader.next_bool()?);
}
ValueType::Null => json_reader.next_null()?,
}
if stack.is_empty() {
break;
}
}
json_reader.consume_trailing_whitespace()?;
Ok(())
}
group.bench_with_input("struson-read", json, |b, json| {
b.iter(|| {
call_unwrap(|| {
let json_reader = JsonStreamReader::new_custom(
json.as_bytes(),
ReaderSettings {
max_nesting_depth: None,
..Default::default()
},
);
struson_read(json_reader)
});
})
});
if include_no_path_tracking {
group.bench_with_input("struson-read (no path tracking)", json, |b, json| {
b.iter(|| {
call_unwrap(|| {
let json_reader = JsonStreamReader::new_custom(
json.as_bytes(),
ReaderSettings {
track_path: false,
max_nesting_depth: None,
..Default::default()
},
);
struson_read(json_reader)
});
})
});
}
fn serde_skip<'a, R: Read<'a>>(read: R) {
struct UnitVisitor;
impl Visitor<'_> for UnitVisitor {
type Value = ();
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "unit")
}
fn visit_unit<E>(self) -> Result<Self::Value, E> {
Ok(())
}
}
let mut deserializer = serde_json::de::Deserializer::new(read);
deserializer.deserialize_ignored_any(UnitVisitor).unwrap();
deserializer.end().unwrap();
}
group.bench_with_input("serde-skip (reader)", json, |b, json| {
b.iter(|| {
serde_skip(IoRead::new(json.as_bytes()));
})
});
group.bench_with_input("serde-skip (string)", json, |b, json| {
b.iter(|| {
serde_skip(StrRead::new(json));
})
});
group.finish();
}
fn benchmark_large_array(c: &mut Criterion) {
let json = format!(
"[{}true]",
"true, false, null, 12345689.123e12, \"abcdabcdabcdabcd\",".repeat(1000)
);
bench_compare(c, "read-large-array", &json, true);
}
fn benchmark_nested_object(c: &mut Criterion) {
let count = 1000;
let json = r#"{"member name":"#.repeat(count) + "true" + "}".repeat(count).as_str();
bench_compare(c, "read-nested-object", &json, true);
}
fn benchmark_nested_object_pretty(c: &mut Criterion) {
let count = 1000;
let mut json = "{".to_owned();
for i in 1..=count {
json.push('\n');
json.push_str(" ".repeat(i).as_str());
json.push_str(r#""member name": {"#);
}
for i in (0..=count).rev() {
json.push('\n');
json.push_str(" ".repeat(i).as_str());
json.push('}');
}
bench_compare(c, "read-nested-object-pretty", &json, true);
}
fn bench_compare_string_reading(c: &mut Criterion, name: &str, json: &str) {
let mut group = c.benchmark_group(name);
group.bench_with_input("struson", json, |b, json| {
b.iter(|| {
call_unwrap(|| {
let mut json_reader = JsonStreamReader::new(json.as_bytes());
black_box(json_reader.next_str()?);
json_reader.consume_trailing_whitespace()?;
Ok(())
});
})
});
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = ();
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "a string")
}
fn visit_borrowed_str<E: serde::de::Error>(self, v: &'de str) -> Result<Self::Value, E> {
black_box(v);
Ok(())
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
black_box(v);
Ok(())
}
fn visit_string<E: serde::de::Error>(self, v: String) -> Result<Self::Value, E> {
black_box(v);
Ok(())
}
}
fn serde_read<'a, R: Read<'a>, F: FnOnce(&mut serde_json::de::Deserializer<R>)>(
read: R,
read_function: F,
) {
let mut deserializer = serde_json::de::Deserializer::new(read);
read_function(&mut deserializer);
deserializer.end().unwrap();
}
group.bench_with_input("serde-str (reader)", json, |b, json| {
b.iter(|| {
serde_read(IoRead::new(json.as_bytes()), |deserializer| {
deserializer.deserialize_str(StringVisitor).unwrap()
});
})
});
group.bench_with_input("serde-string (reader)", json, |b, json| {
b.iter(|| {
serde_read(IoRead::new(json.as_bytes()), |deserializer| {
deserializer.deserialize_string(StringVisitor).unwrap()
});
})
});
group.bench_with_input("serde-str (string)", json, |b, json| {
b.iter(|| {
serde_read(StrRead::new(json), |deserializer| {
deserializer.deserialize_str(StringVisitor).unwrap()
});
})
});
group.bench_with_input("serde-string (string)", json, |b, json| {
b.iter(|| {
serde_read(StrRead::new(json), |deserializer| {
deserializer.deserialize_string(StringVisitor).unwrap()
});
})
});
group.finish();
}
fn benchmark_large_ascii_string(c: &mut Criterion) {
let json = format!("\"{}\"", "this is a test string".repeat(10_000));
bench_compare(
c,
"read-large-ascii-string",
&json,
false,
);
bench_compare_string_reading(c, "read-large-ascii-string (string reading)", &json);
}
fn benchmark_large_unicode_string(c: &mut Criterion) {
let json = format!(
"\"{}\"",
"ab\u{0080}cd\u{0800}ef\u{1234}gh\u{10FFFF}".repeat(10_000)
);
bench_compare(
c,
"read-large-unicode-string",
&json,
false,
);
bench_compare_string_reading(c, "read-large-unicode-string (string reading)", &json);
}
fn benchmark_escapes_string(c: &mut Criterion) {
let json = format!(
"\"{}\"",
r#"a\nb\tc\\d\"e\u0000f\u0080g\u0800h\u1234i\uD800\uDC00"#.repeat(10_000)
);
bench_compare(
c,
"read-large-escapes-string",
&json,
false,
);
bench_compare_string_reading(c, "read-large-escapes-string (string reading)", &json);
}
criterion_group!(
benches,
benchmark_large_array,
benchmark_nested_object,
benchmark_nested_object_pretty,
benchmark_large_ascii_string,
benchmark_large_unicode_string,
benchmark_escapes_string
);
criterion_main!(benches);