Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,345 @@
|
||||
use super::Json;
|
||||
use std::{hint::black_box, time::Instant};
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn document_string_cost() {
|
||||
use crate::wire::document::{self, Document, Value};
|
||||
type Decode = fn(&str) -> Document;
|
||||
let operations: [(&str, Decode); 2] = [
|
||||
("dever", |source| document::parse(source).unwrap()),
|
||||
// 独立 Serde 解码保留相同的单节点结果和字节预算。
|
||||
("rust", |source| {
|
||||
assert!(source.len() <= document::MAX_BYTES);
|
||||
Document {
|
||||
root: 0,
|
||||
nodes: vec![Value::String(serde_json::from_str(source).unwrap())],
|
||||
}
|
||||
}),
|
||||
];
|
||||
println!("\ncase,sample,implementation,iterations,elapsed_ns,checksum");
|
||||
for (name, text) in [
|
||||
("short", "hello".to_owned()),
|
||||
("ascii", "a".repeat(8192)),
|
||||
("unicode", "雪🙂".repeat(1024)),
|
||||
("escaped", "雪\"\\\n".repeat(1024)),
|
||||
] {
|
||||
let source = serde_json::to_string(&text).unwrap();
|
||||
let expected = Document {
|
||||
root: 0,
|
||||
nodes: vec![Value::String(text.clone())],
|
||||
};
|
||||
for (_, operation) in operations {
|
||||
assert_eq!(operation(&source), expected);
|
||||
for _ in 0..100 {
|
||||
black_box(operation(&source));
|
||||
}
|
||||
}
|
||||
let iterations = if name == "short" { 10000 } else { 1000 };
|
||||
for sample in 0..15 {
|
||||
for offset in 0..operations.len() {
|
||||
let (implementation, operation) = operations[(sample + offset) % operations.len()];
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..iterations {
|
||||
let parsed = black_box(operation(black_box(&source)));
|
||||
let Value::String(value) = &parsed.nodes[parsed.root] else {
|
||||
panic!("expected string node");
|
||||
};
|
||||
checksum += value.len();
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, text.len() * iterations);
|
||||
println!(
|
||||
"document_string_{name},{sample},{implementation},{iterations},{elapsed},{checksum}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn encoded_fragment_cost() {
|
||||
type Encode = fn(&str, bool) -> String;
|
||||
println!("\ncase,sample,implementation,iterations,elapsed_ns,checksum");
|
||||
for bytes in [64, 4096, 65536] {
|
||||
let text = "a".repeat(bytes);
|
||||
for nested in [false, true] {
|
||||
let operations: [(&str, Encode); 4] = [
|
||||
("proven", |text, nested| {
|
||||
let value = Json::encode_proven(&(text, 7i64), 3, 1).unwrap();
|
||||
if nested {
|
||||
Json::array(&[value]).unwrap().into_text()
|
||||
} else {
|
||||
value.into_text()
|
||||
}
|
||||
}),
|
||||
("shaped", |text, nested| {
|
||||
let value = Json::encode(&(text, 7i64)).unwrap();
|
||||
if nested {
|
||||
Json::array(&[value]).unwrap().into_text()
|
||||
} else {
|
||||
value.into_text()
|
||||
}
|
||||
}),
|
||||
// 一次写完用于观察融合输出的成本,不能当作中间 Json 的包装开销。
|
||||
("rust_direct", |text, nested| {
|
||||
assert!(text.len() <= (crate::wire::document::MAX_BYTES - 32) / 6);
|
||||
if nested {
|
||||
serde_json::to_string(&[(text, 7i64)]).unwrap()
|
||||
} else {
|
||||
serde_json::to_string(&(text, 7i64)).unwrap()
|
||||
}
|
||||
}),
|
||||
// 与 Dever 一样先持有编码结果,再用标准库写入外层数组。
|
||||
("rust_materialized", |text, nested| {
|
||||
assert!(text.len() <= (crate::wire::document::MAX_BYTES - 32) / 6);
|
||||
let value = serde_json::to_string(&(text, 7i64)).unwrap();
|
||||
if nested {
|
||||
let mut output = String::with_capacity(value.len() + 2);
|
||||
output.push('[');
|
||||
output.push_str(&value);
|
||||
output.push(']');
|
||||
output
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}),
|
||||
];
|
||||
let expected = operations[2].1(&text, nested);
|
||||
for (_, operation) in operations {
|
||||
assert_eq!(operation(&text, nested), expected);
|
||||
for _ in 0..100 {
|
||||
black_box(operation(&text, nested));
|
||||
}
|
||||
}
|
||||
let iterations = if bytes == 64 { 10_000 } else { 1000 };
|
||||
for sample in 0..15 {
|
||||
for offset in 0..operations.len() {
|
||||
let (name, operation) = operations[(sample + offset) % operations.len()];
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..iterations {
|
||||
checksum += black_box(operation(black_box(&text), nested)).len();
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, expected.len() * iterations);
|
||||
println!(
|
||||
"encoded_{bytes}_{nested},{sample},{name},{iterations},{elapsed},{checksum}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn command_json_decode_cost() {
|
||||
use crate::wire;
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Input {
|
||||
source: Json,
|
||||
}
|
||||
#[derive(serde::Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
struct Native {
|
||||
source: Box<serde_json::value::RawValue>,
|
||||
}
|
||||
type Decoder = fn(&str) -> usize;
|
||||
let decoders: [(&str, Decoder); 3] = [
|
||||
("before", |source| {
|
||||
wire::decode::<Input>(source).unwrap().source.as_str().len()
|
||||
}),
|
||||
("after", |source| {
|
||||
wire::decode_json(source, |value: &Input, budget| {
|
||||
budget.open()?;
|
||||
budget.value(1)?;
|
||||
budget.json(&value.source)?;
|
||||
budget.close();
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()
|
||||
.source
|
||||
.as_str()
|
||||
.len()
|
||||
}),
|
||||
// 原生 RawValue 不做去重和结构预算,只提供语法解码成本下界。
|
||||
("rust_syntax_only", |source| {
|
||||
serde_json::from_str::<Native>(source)
|
||||
.unwrap()
|
||||
.source
|
||||
.get()
|
||||
.len()
|
||||
}),
|
||||
];
|
||||
println!("\ncase,sample,implementation,iterations,elapsed_ns,checksum");
|
||||
for (name, text) in [
|
||||
("short", "a".repeat(64)),
|
||||
("plain_65536", "a".repeat(65536)),
|
||||
("escaped", "雪\"\\\n".repeat(1024)),
|
||||
] {
|
||||
let payload = serde_json::to_string(&text).unwrap();
|
||||
let source = format!("{{\"source\":{payload}}}");
|
||||
let iterations = if name == "short" { 10_000 } else { 1000 };
|
||||
for (_, decode) in decoders {
|
||||
for _ in 0..100 {
|
||||
assert_eq!(black_box(decode(&source)), payload.len());
|
||||
}
|
||||
}
|
||||
for sample in 0..15 {
|
||||
for offset in 0..decoders.len() {
|
||||
let (label, decode) = decoders[(sample + offset) % decoders.len()];
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..iterations {
|
||||
checksum += black_box(decode(black_box(&source)));
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, payload.len() * iterations);
|
||||
println!("command_json_{name},{sample},{label},{iterations},{elapsed},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn parse_object_keys_cost() {
|
||||
println!("\ncase,sample,iterations,elapsed_ns,checksum");
|
||||
for (name, suffix) in [
|
||||
("short", String::new()),
|
||||
("long", "field".repeat(32)),
|
||||
("escaped", "雪\"\\\n".repeat(16)),
|
||||
] {
|
||||
let fields = (0..256)
|
||||
.map(|index| (format!("key_{index:03}_{suffix}"), index))
|
||||
.collect::<std::collections::BTreeMap<_, _>>();
|
||||
let source = serde_json::to_string(&fields).unwrap();
|
||||
assert_eq!(Json::parse(&source).unwrap().as_str(), source);
|
||||
for _ in 0..100 {
|
||||
black_box(Json::parse(&source).unwrap());
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..1000 {
|
||||
let parsed = Json::parse(black_box(&source)).unwrap();
|
||||
checksum += black_box(parsed.as_str().len());
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, source.len() * 1000);
|
||||
println!("json_keys_{name},{sample},1000,{elapsed},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn parse_text_json_cost() {
|
||||
println!("\ncase,sample,iterations,elapsed_ns,checksum");
|
||||
for (name, text) in [
|
||||
("ascii", "a".repeat(8192)),
|
||||
("unicode", "雪🙂".repeat(1024)),
|
||||
("escaped", "雪\"\\\n".repeat(1024)),
|
||||
] {
|
||||
let source = serde_json::to_string(&text).unwrap();
|
||||
assert_eq!(Json::parse(&source).unwrap().as_text().unwrap(), text);
|
||||
for _ in 0..100 {
|
||||
black_box(Json::parse(&source).unwrap());
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..1000 {
|
||||
let parsed = Json::parse(black_box(&source)).unwrap();
|
||||
checksum += black_box(parsed.as_str().len());
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, source.len() * 1000);
|
||||
println!("json_parse_{name},{sample},1000,{elapsed},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn deserialize_raw_json_cost() {
|
||||
println!("\ncase,sample,iterations,elapsed_ns,checksum");
|
||||
let values = (0..256).collect::<Vec<i64>>();
|
||||
let text = serde_json::to_string(&"雪\"\\".repeat(512)).unwrap();
|
||||
let array = format!(r#"{{"numbers":{values:?},"huge":1e9999}}"#);
|
||||
let nested = "[".repeat(32) + &array + &"]".repeat(32);
|
||||
for (name, source) in [("text", text), ("array", array), ("nested", nested)] {
|
||||
let parsed: Json = serde_json::from_str(&source).unwrap();
|
||||
assert_eq!(parsed.as_str(), source);
|
||||
for _ in 0..100 {
|
||||
black_box(serde_json::from_str::<Json>(&source).unwrap());
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..1000 {
|
||||
let parsed: Json = serde_json::from_str(black_box(&source)).unwrap();
|
||||
checksum += black_box(parsed.as_str().len());
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, source.len() * 1000);
|
||||
println!("json_deserialize_{name},{sample},1000,{elapsed},{checksum}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "explicit release performance probe"]
|
||||
fn property_lookup_cost() {
|
||||
println!("\ncase,sample,iterations,elapsed_ns,checksum");
|
||||
for (name, count, suffix) in [
|
||||
("small", 4, ""),
|
||||
("wide", 256, ""),
|
||||
("escaped", 256, "雪\\\""),
|
||||
] {
|
||||
let names = (0..count)
|
||||
.map(|index| format!("key_{index:03}_{suffix}"))
|
||||
.collect::<Vec<_>>();
|
||||
let fields = names
|
||||
.iter()
|
||||
.map(|key| format!("{}:1e9999", serde_json::to_string(key).unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
let source = format!("{{{}}}", fields.join(","));
|
||||
let value = Json::parse(&source).unwrap();
|
||||
let keys = [
|
||||
names[0].as_str(),
|
||||
&names[count / 2],
|
||||
&names[count - 1],
|
||||
"missing",
|
||||
];
|
||||
let lookup = || {
|
||||
keys.iter()
|
||||
.map(|key| {
|
||||
value
|
||||
.property(black_box(key))
|
||||
.unwrap()
|
||||
.map_or(0, |child| black_box(child.as_str().len()))
|
||||
})
|
||||
.sum::<usize>()
|
||||
};
|
||||
assert_eq!(lookup(), 18);
|
||||
for _ in 0..100 {
|
||||
black_box(lookup());
|
||||
}
|
||||
for sample in 0..15 {
|
||||
let started = Instant::now();
|
||||
let mut checksum = 0;
|
||||
for _ in 0..1000 {
|
||||
checksum += black_box(lookup());
|
||||
}
|
||||
let elapsed = started.elapsed().as_nanos();
|
||||
assert_eq!(checksum, 18000);
|
||||
println!("json_property_{name},{sample},1000,{elapsed},{checksum}");
|
||||
}
|
||||
assert_eq!(value.as_str(), source);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user