Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+258
View File
@@ -0,0 +1,258 @@
use dever_runtime::wire::serde::{self, Deserialize, Serialize};
use dever_runtime::{
api::{Request, Response},
task::{Limits, Runtime, Scope},
};
use std::future::Future;
struct Input<'scope> {
request: Request,
peer: std::net::SocketAddr,
scope: &'scope Scope<'scope>,
}
fn fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo {
message: "HTTP input failure",
source: "api-reference",
line: 1,
column: 1,
}
}
// 此样例不挂起;直接轮询实际生成入口,计时不含网络与调度器启动。
fn generated(values: &[Input<'_>]) -> Result<Vec<u8>, Fault> {
let Some(value) = values.first() else {
return Ok(Vec::new());
};
let future = async {
if value.request.uri().path() == "/bench/wire/count" {
GENERATED_LIST(&value.request, value.peer, value.scope).await
} else {
GENERATED_HTTP(&value.request, value.peer, value.scope).await
}
};
let mut future = std::pin::pin!(future);
let mut context = std::task::Context::from_waker(std::task::Waker::noop());
let std::task::Poll::Ready(response) = future.as_mut().poll(&mut context) else {
panic!("pure HTTP fixture unexpectedly suspended");
};
if response.status() != 200 {
return Err(fault(response.status()));
}
Ok(response.into_body().into())
}
#[derive(Deserialize, Serialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Payload {
text: String,
value: i64,
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Arguments {
payload: Object<Payload>,
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct ListArguments {
values: Integers,
}
struct Integers(Vec<i64>);
impl<'input> Deserialize<'input> for Integers {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
struct Visitor;
impl<'input> serde::de::Visitor<'input> for Visitor {
type Value = Integers;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an integer array")
}
fn visit_seq<Access: serde::de::SeqAccess<'input>>(
self,
mut access: Access,
) -> Result<Integers, Access::Error> {
let mut values = Vec::new();
while let Some(value) = access.next_element()? {
// 根对象和数组各占一个值;检查与 Serde 解码在同一次遍历中完成。
if values.len() == 65536 - 2 {
return Err(serde::de::Error::custom("value limit"));
}
values.push(value);
}
Ok(Integers(values))
}
}
decoder.deserialize_seq(Visitor)
}
}
struct Object<Value>(Value);
impl<'input, Value: Deserialize<'input>> Deserialize<'input> for Object<Value> {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
struct Visitor<Value>(std::marker::PhantomData<Value>);
impl<'input, Value: Deserialize<'input>> serde::de::Visitor<'input> for Visitor<Value> {
type Value = Value;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(
self,
access: Access,
) -> Result<Value, Access::Error> {
Value::deserialize(serde::de::value::MapAccessDeserializer::new(access))
}
}
decoder
.deserialize_map(Visitor(std::marker::PhantomData))
.map(Self)
}
}
// 独立使用 Serde;固定记录保证结构预算,字节预算在唯一输入/输出边界处理。
fn reference(values: &[Input<'_>]) -> Result<Vec<u8>, Fault> {
let Some(value) = values.first() else {
return Ok(Vec::new());
};
let request = &value.request;
let mut headers = request.headers().get_all("content-type").iter();
let content = headers.next().ok_or_else(|| fault("content type"))?;
let content = std::str::from_utf8(content.as_bytes()).map_err(fault)?;
if !content
.split(';')
.next()
.unwrap()
.trim()
.eq_ignore_ascii_case("application/json")
|| headers.next().is_some()
{
return Err(fault("content type"));
}
if request.body().len() > 16 * 1024 * 1024 {
return Err(fault("input size"));
}
let text = std::str::from_utf8(request.body()).map_err(fault)?;
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Envelope<Value> {
code: u16,
message: &'static str,
data: Value,
}
struct Output(Vec<u8>);
impl std::io::Write for Output {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
if bytes.len() > 16 * 1024 * 1024 - self.0.len() {
return Err(std::io::Error::other("output size"));
}
self.0.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
let mut output = Output(Vec::with_capacity(128));
if request.uri().path() == "/bench/wire/count" {
let arguments: Object<ListArguments> = serde_json::from_str(text).map_err(fault)?;
serde_json::to_writer(
&mut output,
&Envelope {
code: 0,
message: "ok",
data: arguments.0.values.0.len() as i64,
},
)
.map_err(fault)?;
} else {
let arguments: Object<Arguments> = serde_json::from_str(text).map_err(fault)?;
serde_json::to_writer(
&mut output,
&Envelope {
code: 0,
message: "ok",
data: arguments.0.payload.0,
},
)
.map_err(fault)?;
}
let mut response = Response::new(output.0.into());
response.headers_mut().insert(
hyper::header::CONTENT_TYPE,
hyper::header::HeaderValue::from_static("application/json; charset=utf-8"),
);
Ok(response.into_body().into())
}
fn main() {
println!("case,sample,implementation,elapsed_ns,checksum");
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 8,
})
.unwrap();
runtime
.run(async |scope| {
for size in [64, 4096, 65536] {
let payload = Payload {
text: "雪\\\"".repeat(size / 5),
value: 42,
};
let body = format!(
"{{\"payload\":{}}}",
serde_json::to_string(&payload).unwrap()
)
.into_bytes();
let values = [Input {
request: hyper::Request::builder()
.method("POST")
.uri("/bench/wire/echo")
.header("content-type", "application/json")
.body(body.into())
.unwrap(),
peer: ([127, 0, 0, 1], 12345).into(),
scope,
}];
compare(
&format!("http_fixed_record_{size}"),
&values,
200,
generated,
reference,
|body| body.len() as u64,
);
}
for size in [16, 1024, 8192, 32768] {
let body = format!("{{\"values\":[{}]}}", vec!["42"; size].join(",")).into_bytes();
let values = [Input {
request: hyper::Request::builder()
.method("POST")
.uri("/bench/wire/count")
.header("content-type", "application/json")
.body(body.into())
.unwrap(),
peer: ([127, 0, 0, 1], 12345).into(),
scope,
}];
compare(
&format!("http_integer_list_{size}"),
&values,
200,
generated,
reference,
|body| body.len() as u64,
);
}
})
.unwrap();
}
+274
View File
@@ -0,0 +1,274 @@
use dever_runtime::wire::{
self,
serde::{self, Deserialize, Serialize},
};
struct Object<Value>(Value);
impl<'input, Value: Deserialize<'input>> Deserialize<'input> for Object<Value> {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
wire::object(decoder).map(Self)
}
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Greeting {
name: String,
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Integer {
value: i64,
}
#[derive(Deserialize, Serialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Profile {
id: Id,
name: String,
scores: Vec<i64>,
ratio: Option<f64>,
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Echo {
value: Object<Profile>,
}
fn fault(_: String) -> Fault {
&FaultInfo {
message: "command failure",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn greeting_generated(values: &[&str]) -> Result<String, Fault> {
GENERATED_GREETING(values.first().copied().unwrap_or(r#"{"name":""}"#)).map_err(fault)
}
fn greeting_reference(values: &[&str]) -> Result<String, Fault> {
let input: Object<Greeting> =
wire::decode(values.first().copied().unwrap_or(r#"{"name":""}"#)).map_err(fault)?;
let mut result = String::with_capacity(input.0.name.len() + 9);
result.push_str("[Hello, ");
result.push_str(&input.0.name);
result.push(']');
wire::response(&result).map_err(fault)
}
fn echo_generated(values: &[&str]) -> Result<String, Fault> {
GENERATED_ECHO(
values
.first()
.copied()
.unwrap_or(r#"{"value":{"id":"","name":"","scores":[]}}"#),
)
.map_err(fault)
}
fn echo_reference(values: &[&str]) -> Result<String, Fault> {
let input: Object<Echo> = wire::decode(
values
.first()
.copied()
.unwrap_or(r#"{"value":{"id":"","name":"","scores":[]}}"#),
)
.map_err(fault)?;
let result = input.0.value.0;
if result.ratio.is_some_and(|value| !value.is_finite()) {
return Err(fault("non-finite Float".into()));
}
wire::response(&result).map_err(fault)
}
fn double_generated(values: &[&str]) -> Result<String, Fault> {
GENERATED_DOUBLE(values.first().copied().unwrap_or(r#"{"value":0}"#)).map_err(fault)
}
fn double_reference(values: &[&str]) -> Result<String, Fault> {
let input: Object<Integer> =
wire::decode(values.first().copied().unwrap_or(r#"{"value":0}"#)).map_err(fault)?;
let result = input
.0
.value
.checked_mul(2)
.ok_or_else(|| fault("overflow".into()))?;
wire::response(&result).map_err(fault)
}
// 独立对照只处理这里的固定标量 record;结构预算由这两个协议本身保证。
// 不调用 Dever 的 object/decode/response,也不替换原来的共享运行库对照。
fn rust_input<Value: serde::de::DeserializeOwned>(input: &str) -> Result<Value, Fault> {
if input.len() > 16 * 1024 * 1024 {
return Err(fault("input limit".into()));
}
struct Record<Value>(std::marker::PhantomData<Value>);
impl<'input, Value: Deserialize<'input>> serde::de::Visitor<'input> for Record<Value> {
type Value = Value;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(
self,
access: Access,
) -> Result<Value, Access::Error> {
Value::deserialize(serde::de::value::MapAccessDeserializer::new(access))
}
}
let mut decoder = serde_json::Deserializer::from_str(input);
let value =
serde::Deserializer::deserialize_map(&mut decoder, Record(std::marker::PhantomData))
.map_err(|error| fault(error.to_string()))?;
decoder.end().map_err(|error| fault(error.to_string()))?;
Ok(value)
}
fn rust_response<Value: Serialize>(data: Value) -> Result<String, Fault> {
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Response<Value> {
code: u16,
message: &'static str,
data: Value,
}
struct Buffer(Vec<u8>);
impl std::io::Write for Buffer {
fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
if bytes.len() > 16 * 1024 * 1024 - self.0.len() {
return Err(std::io::Error::other("output limit"));
}
self.0.extend_from_slice(bytes);
Ok(bytes.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
// 与 serde_json::to_vec 相同;独立 Rust 参照也避免短输出反复扩容。
let mut output = Buffer(Vec::with_capacity(128));
serde_json::to_writer(
&mut output,
&Response {
code: 0,
message: "ok",
data,
},
)
.map_err(|error| fault(error.to_string()))?;
Ok(String::from_utf8(output.0).unwrap())
}
fn greeting_rust(values: &[&str]) -> Result<String, Fault> {
let input: Greeting = rust_input(values.first().copied().unwrap_or(r#"{"name":""}"#))?;
let mut result = String::with_capacity(input.name.len() + 9);
result.push_str("[Hello, ");
result.push_str(&input.name);
result.push(']');
rust_response(result)
}
fn double_rust(values: &[&str]) -> Result<String, Fault> {
let input: Integer = rust_input(values.first().copied().unwrap_or(r#"{"value":0}"#))?;
rust_response(
input
.value
.checked_mul(2)
.ok_or_else(|| fault("overflow".into()))?,
)
}
fn main() {
for input in [
r#"{"value":9223372036854775807}"#,
r#"{"value":1,"value":2}"#,
"[1]",
"{}",
] {
assert_eq!(
double_generated(&[input]).is_err(),
double_reference(&[input]).is_err()
);
assert_eq!(
double_generated(&[input]).is_err(),
double_rust(&[input]).is_err()
);
}
for input in [
r#"{"value":["a","b",[],null]}"#,
r#"{"value":{"id":"a","name":"b","scores":[],"extra":1}}"#,
] {
assert_eq!(
echo_generated(&[input]).is_err(),
echo_reference(&[input]).is_err()
);
}
let long_name = "雪".repeat(2048);
let long_greeting = format!("{{\"name\":\"{long_name}\"}}");
let echo = format!(
"{{\"value\":{{\"id\":\"a\",\"name\":\"{long_name}\",\"scores\":[1,2,3],\"ratio\":0.5}}}}"
);
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"command_greet_short",
&[r#"{"name":"Dever"}"#],
50000,
greeting_generated,
greeting_reference,
|value| value.len() as u64,
);
compare(
"command_greet_long",
&[long_greeting.as_str()],
3000,
greeting_generated,
greeting_reference,
|value| value.len() as u64,
);
compare(
"command_echo",
&[echo.as_str()],
3000,
echo_generated,
echo_reference,
|value| value.len() as u64,
);
compare(
"command_double",
&[r#"{"value":21}"#],
50000,
double_generated,
double_reference,
|value| value.len() as u64,
);
compare(
"command_greet_short_independent",
&[r#"{"name":"Dever"}"#],
50000,
greeting_generated,
greeting_rust,
|value| value.len() as u64,
);
compare(
"command_greet_long_independent",
&[long_greeting.as_str()],
3000,
greeting_generated,
greeting_rust,
|value| value.len() as u64,
);
compare(
"command_double_independent",
&[r#"{"value":21}"#],
50000,
double_generated,
double_rust,
|value| value.len() as u64,
);
}
+103
View File
@@ -0,0 +1,103 @@
// 使用 auth 样例实际生成的 RestCreate0;独立 Rust 直接复用 Serde,合同相同。
// 测量仅包含请求对象解码,不包括字段业务约束、认证、SQL 或网络。
use dever_runtime::wire::serde::{self, Deserialize};
use dever_runtime::wire::serde_json;
fn fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo { message: "REST input failure", source: "rest-reference", line: 1, column: 1 }
}
enum Status { Active, Disabled }
impl<'input> Deserialize<'input> for Status {
fn deserialize<Decoder: serde::Deserializer<'input>>(decoder: Decoder) -> Result<Self, Decoder::Error> {
struct Choice;
impl<'input> serde::de::Visitor<'input> for Choice {
type Value = Status;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { output.write_str("a status name") }
fn visit_str<Error: serde::de::Error>(self, value: &str) -> Result<Status, Error> {
match value {
"Active" => Ok(Status::Active),
"Disabled" => Ok(Status::Disabled),
_ => Err(Error::custom("unknown status")),
}
}
}
decoder.deserialize_str(Choice)
}
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Profile {
name: String,
bio: Option<String>,
#[serde(default = "rank")]
rank: i64,
#[serde(default = "tag")]
tag: Option<String>,
#[serde(default = "status")]
status: Status,
}
fn rank() -> i64 { 7 }
fn tag() -> Option<String> { Some("automatic".into()) }
fn status() -> Status { Status::Active }
fn generated(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else { return Ok(String::new()); };
let value: RestCreate0 = REST_DECODER(source).map_err(fault)?;
Ok(value.field0)
}
fn reference(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else { return Ok(String::new()); };
if source.len() > 16 * 1024 * 1024 { return Err(fault("byte limit")); }
struct Object;
impl<'input> serde::de::Visitor<'input> for Object {
type Value = Profile;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(self, access: Access) -> Result<Profile, Access::Error> {
Profile::deserialize(serde::de::value::MapAccessDeserializer::new(access))
}
}
let mut decoder = serde_json::Deserializer::from_str(source);
let value = serde::Deserializer::deserialize_map(&mut decoder, Object).map_err(fault)?;
decoder.end().map_err(fault)?;
// 固定五个标量字段,类型已证明层数和值数;保留其正常构造与释放。
let Profile { name, bio, rank, tag, status } = value;
drop((bio, rank, tag, status));
Ok(name)
}
fn main() {
println!("workload,sample,implementation,nanos,checksum");
for source in [
r#"{"name":"Dever"}"#,
r#"{"name":"Dever","bio":null,"tag":null,"status":"Disabled"}"#,
] {
assert_eq!(generated(&[source.into()]).unwrap(), "Dever");
assert_eq!(reference(&[source.into()]).unwrap(), "Dever");
}
for source in [
"[]", "null", "{}", r#"{"name":"x","name":"y"}"#,
r#"{"name":"x","na\u006de":"y"}"#, r#"{"name":"x","extra":1}"#,
r#"{"name":"x","rank":null}"#, r#"{"name":"x","status":"Other"}"#,
r#"{"name":"x","status":{"Active":null}}"#,
r#"{"name":"x","bio":"\ud800"}"#, r#"{"name":"x"} null"#,
] {
assert!(generated(&[source.into()]).is_err(), "{source}");
assert!(reference(&[source.into()]).is_err(), "{source}");
}
for (name, bio) in [
("short", "x".repeat(16)),
("plain_4096", "x".repeat(4096)),
("plain_65536", "x".repeat(65536)),
("escaped", "雪\"\\\n".repeat(1024)),
] {
let input = vec![serde_json::json!({"name":"Dever","bio":bio,"rank":7,"tag":"automatic","status":"Active"}).to_string()];
compare(&format!("rest_input_{name}"), &input, 1000, generated, reference, |name| name.len() as u64);
}
}
@@ -0,0 +1,231 @@
use dever_runtime::wire::serde::{self, Deserialize};
use dever_runtime::wire::serde_json::{self, value::RawValue};
fn fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo {
message: "REST JSON input",
source: "rest-json-reference",
line: 1,
column: 1,
}
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Fields {
value: Box<RawValue>,
extra: Option<Box<RawValue>>,
}
fn generated(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
let value: RAW_ARGS = dever_runtime::wire::deserialize(source).map_err(fault)?;
Ok(value.field0.into_text())
}
fn reference(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
struct Object;
impl<'input> serde::de::Visitor<'input> for Object {
type Value = Fields;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(
self,
access: Access,
) -> Result<Fields, Access::Error> {
Fields::deserialize(serde::de::value::MapAccessDeserializer::new(access))
}
}
let mut decoder = serde_json::Deserializer::from_str(source);
let value = serde::Deserializer::deserialize_map(&mut decoder, Object).map_err(fault)?;
decoder.end().map_err(fault)?;
drop(value.extra);
Ok(Box::<str>::from(value.value).into())
}
// 保留原始参照,单独改变入口或字段表示,定位短输入的固定开销。
fn generated_decoder(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
let mut decoder = serde_json::Deserializer::from_str(source);
let value = RAW_ARGS::deserialize(&mut decoder).map_err(fault)?;
decoder.end().map_err(fault)?;
Ok(value.field0.into_text())
}
struct RawFields(Fields);
impl<'input> Deserialize<'input> for RawFields {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
struct Object;
impl<'input> serde::de::Visitor<'input> for Object {
type Value = RawFields;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(
self,
access: Access,
) -> Result<RawFields, Access::Error> {
Fields::deserialize(serde::de::value::MapAccessDeserializer::new(access))
.map(RawFields)
}
}
decoder.deserialize_map(Object)
}
}
fn reference_entry(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
let value: RawFields = serde_json::from_str(source).map_err(fault)?;
Ok(Box::<str>::from(value.0.value).into())
}
fn raw_text<'input, Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<String, Decoder::Error> {
Box::<RawValue>::deserialize(decoder).map(|raw| Box::<str>::from(raw).into())
}
fn optional_raw_text<'input, Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Option<String>, Decoder::Error> {
Option::<Box<RawValue>>::deserialize(decoder)
.map(|raw| raw.map(|raw| Box::<str>::from(raw).into()))
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct TextFields {
#[serde(deserialize_with = "raw_text")]
value: String,
#[serde(default, deserialize_with = "optional_raw_text")]
extra: Option<String>,
}
fn reference_text(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
struct Object;
impl<'input> serde::de::Visitor<'input> for Object {
type Value = TextFields;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an object")
}
fn visit_map<Access: serde::de::MapAccess<'input>>(
self,
access: Access,
) -> Result<TextFields, Access::Error> {
TextFields::deserialize(serde::de::value::MapAccessDeserializer::new(access))
}
}
let mut decoder = serde_json::Deserializer::from_str(source);
let value = serde::Deserializer::deserialize_map(&mut decoder, Object).map_err(fault)?;
decoder.end().map_err(fault)?;
drop(value.extra);
Ok(value.value)
}
fn generated_output(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
let value: RAW_ARGS = dever_runtime::wire::deserialize(source).map_err(fault)?;
GENERATED_RAW_ENCODE(&value.field0).map_err(fault)
}
fn reference_output(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else {
return Ok(String::new());
};
let value: RawFields = serde_json::from_str(source).map_err(fault)?;
serde_json::to_string(&value.0.value).map_err(fault)
}
fn main() {
if std::env::args().any(|argument| argument == "--output-only") {
println!("workload,sample,implementation,nanos,checksum");
output::run();
return;
}
let short_only = std::env::args().any(|argument| argument == "--short");
eprintln!(
"layout,json={},string={},raw={}",
std::mem::size_of::<dever_runtime::wire::json::Json>(),
std::mem::size_of::<String>(),
std::mem::size_of::<Box<RawValue>>()
);
println!("workload,sample,implementation,nanos,checksum");
for (name, text) in [
("short", "x".repeat(16)),
("plain_4096", "x".repeat(4096)),
("plain_65536", "x".repeat(65536)),
("escaped", "雪\"\\\n".repeat(1024)),
] {
if short_only && name != "short" {
continue;
}
let raw = format!(
"{{\"text\":{},\"number\":1e9999}}",
serde_json::to_string(&text).unwrap()
);
let source = format!("{{\"value\":{raw},\"extra\":{raw}}}");
// 整体请求校验在两侧计时外完成;这里单独比较已校验请求的字段解码。
let checked: RAW_ARGS = dever_runtime::wire::decode(&source).unwrap();
assert_eq!(checked.field0.as_str(), raw);
assert_eq!(checked.field1.unwrap().as_str(), raw);
// 短输入每组至少持续数十毫秒,避免亚毫秒样本的调度噪声主导百分比。
let count = if name == "short" { 100000 } else { 1000 };
let values = [source];
compare(
&format!("rest_json_{name}"),
&values,
count,
generated,
reference,
|value| value.len() as u64,
);
compare(
&format!("rest_json_output_{name}"),
&values,
count,
generated_output,
reference_output,
|value| value.len() as u64,
);
}
let raw = r#"{"text":"xxxxxxxxxxxxxxxx","number":1e9999}"#;
let input = [format!("{{\"value\":{raw},\"extra\":{raw}}}")];
for (name, implementation) in [
(
"decoder",
generated_decoder as fn(&[String]) -> Result<String, Fault>,
),
("raw_from_str", reference_entry),
("text_decoder", reference_text),
] {
if short_only && name != "decoder" {
continue;
}
compare(
&format!("rest_json_control_{name}"),
&input,
100000,
implementation,
reference,
|value| value.len() as u64,
);
}
}
@@ -0,0 +1,170 @@
use chrono::Datelike;
use dever_runtime::wire::json::Json;
use dever_runtime::wire::serde::{Serialize, Serializer};
#[derive(Clone)]
struct Timestamp(i64);
impl Serialize for Timestamp {
fn serialize<Encoder: Serializer>(
&self,
encoder: Encoder,
) -> Result<Encoder::Ok, Encoder::Error> {
let value = chrono::DateTime::from_timestamp_millis(self.0)
.filter(|value| (0..=9999).contains(&value.year()))
.ok_or_else(|| dever_runtime::wire::serde::ser::Error::custom("invalid timestamp"))?;
let pattern = if value.timestamp_subsec_millis() == 0 {
"%Y-%m-%dT%H:%M:%SZ"
} else {
"%Y-%m-%dT%H:%M:%S%.3fZ"
};
encoder.collect_str(&value.naive_utc().format(pattern))
}
}
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Row {
id: i64,
created_at: Timestamp,
value: Box<RawValue>,
extra: Option<Box<RawValue>>,
}
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Reply<Value> {
code: u16,
message: &'static str,
data: Value,
}
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Page<Items> {
items: Items,
page: i64,
size: i64,
total: i64,
pages: i64,
}
struct Case {
dever: Vec<RAW_MODEL>,
rust: Vec<Row>,
}
fn detail(values: &[Case]) -> Result<String, Fault> {
let Some(value) = values.first() else {
return Ok(String::new());
};
let row = &value.dever[0];
REST_DETAIL_ENCODE.map_err(fault)
}
fn page(values: &[Case]) -> Result<String, Fault> {
let Some(value) = values.first() else {
return Ok(String::new());
};
let page = Page {
items: value.dever.as_slice(),
page: 1,
size: 20,
total: value.dever.len() as i64,
pages: 1,
};
REST_PAGE_ENCODE.map_err(fault)
}
fn encode<Value: Serialize>(data: Value, count: usize) -> Result<String, Fault> {
if count > 65_536 {
return Err(fault("value budget"));
}
let text = serde_json::to_string(&Reply {
code: 0,
message: "ok",
data,
})
.map_err(fault)?;
if text.len() > 16 * 1024 * 1024 {
return Err(fault("byte budget"));
}
Ok(text)
}
// 固定夹具中的 Json 是两个标量字段的对象;预算在初次构造后复用。
// 独立 Rust 的字段编码、原文输出及响应封装不调用 Dever 运行库。
fn row_values(row: &Row) -> usize {
if row.extra.is_some() { 9 } else { 7 }
}
fn rust_detail(values: &[Case]) -> Result<String, Fault> {
let Some(value) = values.first() else {
return Ok(String::new());
};
encode(&value.rust[0], 3 + row_values(&value.rust[0]))
}
fn rust_page(values: &[Case]) -> Result<String, Fault> {
let Some(value) = values.first() else {
return Ok(String::new());
};
let page = Page {
items: value.rust.as_slice(),
page: 1,
size: 20,
total: value.rust.len() as i64,
pages: 1,
};
// 五层容器固定小于 64;根、响应字段、分页对象与数组共九个值。
encode(page, 9 + value.rust.iter().map(row_values).sum::<usize>())
}
pub(super) fn run() {
for (name, text) in [
("short", "x".repeat(16)),
("plain_4096", "x".repeat(4096)),
("plain_65536", "x".repeat(65536)),
("escaped", "雪\"\\\n".repeat(1024)),
] {
let source = format!(
"{{\"text\":{},\"number\":1e9999}}",
serde_json::to_string(&text).unwrap()
);
let dever = (0..16)
.map(|index| RAW_MODEL {
field0: dever_runtime::database::Id::<RAW_MODEL_ID>(index + 1),
field1: dever_runtime::time::calendar::DateTime::from_millis(0).unwrap(),
field2: Json::parse(&source).unwrap(),
field3: (index % 2 == 0).then(|| Json::parse(&source).unwrap()),
})
.collect();
let rust = (0..16)
.map(|index| Row {
id: index + 1,
created_at: Timestamp(0),
value: serde_json::from_str(&source).unwrap(),
extra: (index % 2 == 0).then(|| serde_json::from_str(&source).unwrap()),
})
.collect();
let values = [Case { dever, rust }];
compare_named(
&format!("rest_detail_output_{name}"),
&values,
if name == "short" { 10000 } else { 1000 },
detail,
rust_detail,
|text| text.len() as u64,
"rust_bounded",
);
compare_named(
&format!("rest_page_output_{name}"),
&values,
if name == "short" { 1000 } else { 100 },
page,
rust_page,
|text| text.len() as u64,
"rust_bounded",
);
}
}
+192
View File
@@ -0,0 +1,192 @@
//! 新场景沿用同一个 compare 构建器;只替换测试入口名,保留正式生成的认证和 Job 分派。
use dever::compiler::{emit, model::Program};
use std::path::Path;
#[path = "../job/build.rs"]
mod jobs;
pub fn run(core: &Path) -> bool {
if std::env::args().any(|value| value == "--rest-json") {
crate::compare(
core,
"auth",
"rest-json",
&(include_str!("../samples.rs").to_owned()
+ include_str!("../application/rest_json.rs")
+ "\nmod output { use super::*;\n"
+ include_str!("../application/rest_output.rs")
+ "\n}\n"),
&[("GENERATED_RAW_ENCODE", "demo.document.app.encode")],
&[],
);
return true;
}
if std::env::args().any(|value| value == "--rest-wire") {
crate::compare(
core,
"auth",
"rest-wire",
&(include_str!("../samples.rs").to_owned() + include_str!("../application/rest.rs")),
&[],
&[],
);
return true;
}
if std::env::args().any(|value| value == "--api-wire") {
crate::compare(
core,
"api-wire",
"api-wire",
&(include_str!("../samples.rs").to_owned() + include_str!("../application/http.rs")),
&[],
&[],
);
return true;
}
let http = std::env::args().any(|value| value == "--auth-http");
if http || std::env::args().any(|value| value == "--auth") {
let reference = [
include_str!("shared.rs"),
include_str!("reference.rs"),
include_str!("measure.rs"),
"mod protected_http { use super::*;\n",
include_str!("http/reference.rs"),
include_str!("http/client.rs"),
include_str!("http/check.rs"),
include_str!("http/measure.rs"),
"}\n",
&format!("const HTTP_BENCH: bool = {http};\n"),
]
.concat();
crate::compare(
core,
"auth-performance",
if http { "auth-http" } else { "auth" },
&reference,
&[
("GENERATED_SEED", "bench.account.app.seed"),
("GENERATED_PERMISSIONS", "bench.account.app.permissions"),
],
&[],
);
true
} else if std::env::args().any(|value| value == "--jobs") {
jobs::run(core);
true
} else {
false
}
}
pub fn metadata(name: &str, program: &Program, reference: &str) -> String {
match name {
"rest-json" => {
let index = program
.rest
.iter()
.position(|entry| {
program.packages
[program.definitions[program.models[entry.model].definition].source]
== "demo.document.model"
})
.unwrap();
let source = emit::application::generate(program);
let route = source
.split_once(&format!("async fn rest{index}("))
.unwrap()
.1;
let detail = route
.split_once("let row = rows.pop().ok_or(dever_runtime::api::Failure(404))?; ")
.unwrap()
.1;
let page = route
.split_once("let page = executor.page(")
.unwrap()
.1
.split_once(".await.map_err(rest_error)?; ")
.unwrap()
.1;
let definition = program.models[program.rest[index].model].definition;
reference
.replace("RAW_ARGS", &format!("RestCreate{index}"))
.replace("RAW_MODEL_ID", &definition.to_string())
.replace("RAW_MODEL", &format!("Type{definition}"))
.replace(
"REST_DETAIL_ENCODE",
detail.split_once(".map_err(Into::into)").unwrap().0,
)
.replace(
"REST_PAGE_ENCODE",
page.split_once(".map_err(Into::into)").unwrap().0,
)
}
"rest-wire" => {
// 比较实际 REST 输入类型及其解码入口,不把数据库和认证计入 codec 耗时。
let source = emit::application::generate(program);
let (_, input) = source
.split_once("let args: RestCreate0 = input.decode(")
.unwrap();
let decoder = input.split_once(')').unwrap().0;
reference.replace("REST_DECODER", decoder)
}
"api-wire" => [
("GENERATED_HTTP", "/bench/wire/echo"),
("GENERATED_LIST", "/bench/wire/count"),
]
.into_iter()
.fold(reference.to_owned(), |source, (marker, path)| {
let index = program
.http
.iter()
.position(|entry| entry.path == path && entry.method == "POST")
.unwrap();
source.replace(marker, &format!("api{index}"))
}),
"auth" | "auth-http" => {
assert_eq!(program.verifiers.len(), 1);
let verifier = &program.verifiers[0];
let account = program
.models
.iter()
.position(|model| {
program.packages[program.definitions[model.definition].source]
== "bench.account.model"
})
.unwrap();
reference
.replace("ACCOUNT_BINDING", &account.to_string())
.replace("AUTH_BINDING", &(program.models.len() + 1).to_string())
.replace("AUTH_USER_MODEL", &verifier.user.1.to_string())
.replace("AUTH_TENANT_MODEL", &verifier.tenant.1.to_string())
.replace(
"GENERATED_HTTP",
&format!(
"api{}",
program
.http
.iter()
.position(|entry| entry.path == "/bench/account/admin/permissions"
&& entry.method == "GET")
.unwrap()
),
)
}
"jobs" => jobs::metadata(program, reference),
_ => reference.into(),
}
}
pub fn application(name: &str, program: &Program) -> String {
if matches!(
name,
"auth" | "auth-http" | "jobs" | "api-wire" | "rest-wire" | "rest-json"
) {
let source = emit::application::generate(program);
assert_eq!(source.matches("fn main() {").count(), 1);
source.replacen("fn main() {", "fn application_main() {", 1)
} else if !program.commands.is_empty() {
emit::application::functions(program)
} else {
String::new()
}
}
+83
View File
@@ -0,0 +1,83 @@
// 正确性检查在计时外覆盖凭据、Provider、角色、路由及严格输入,不能用空 200 掩盖漏做。
fn check_requests(connection: &mut HttpConnection, input: &AuthInput) {
for request in &input.requests[..16] {
check_reply(&connection.request(&request_bytes(request)), 200);
}
check_reply(
&connection.request(&request_bytes(&input.requests[16])),
401,
);
let valid = &input.requests[0];
let mut missing = valid.clone();
missing.headers_mut().remove("cookie");
let mut invalid = valid.clone();
invalid
.headers_mut()
.insert("cookie", "session=v1.invalid-jwt".parse().unwrap());
let mut host = valid.clone();
host.headers_mut()
.insert("host", "other.test".parse().unwrap());
let mut path = valid.clone();
*path.uri_mut() = format!("{}-missing", path.uri()).parse().unwrap();
let mut query = valid.clone();
*query.uri_mut() = format!("{}?unexpected=1", query.uri()).parse().unwrap();
for (request, status) in [
(missing, 401),
(invalid, 401),
(host, 404),
(path, 404),
(query, 400),
] {
check_reply(&connection.request(&request_bytes(&request)), status);
}
}
fn membership(runtime: &Runtime, registry: &Arc<Registry>, input: &AuthInput, grant: bool) {
runtime
.run(async |scope| {
let (mut context, claims) = input
.service
.prepare("admin", &input.requests[0], false, input.now)
.unwrap();
rust_verify(&mut context, claims.unwrap(), scope, registry)
.await
.map_err(|error| error.0)
.unwrap();
let mut database = DbContext::new(registry.clone());
let executor = database.on(AUTH_BINDING, scope).await.unwrap();
if grant {
authorization::grant(executor, scope, &context, 16, "reader")
.await
.unwrap();
} else {
authorization::revoke(executor, scope, &context, 16, "reader")
.await
.unwrap();
}
})
.unwrap();
}
fn check(
runtime: &Runtime,
registry: &Arc<Registry>,
config: &Arc<api::Config>,
input: &AuthInput,
) {
for generated in [true, false] {
with_server(runtime, registry, config, input, generated, |port| {
check_requests(&mut HttpConnection::connect(port), input);
});
}
membership(runtime, registry, input, false);
for generated in [true, false] {
with_server(runtime, registry, config, input, generated, |port| {
let mut connection = HttpConnection::connect(port);
check_reply(
&connection.request(&request_bytes(&input.requests[15])),
403,
);
});
}
membership(runtime, registry, input, true);
}
+91
View File
@@ -0,0 +1,91 @@
// 客户端只连接本轮持有的回环监听器,使用同一 HTTP/1.1 长连接顺序发送请求。
use std::{
io::{BufRead, BufReader, Read, Write},
net::TcpStream,
time::Duration,
};
const ROUTE: &str = "/bench/account/admin/permissions";
const CONTENT_TYPE: &str = "application/json; charset=utf-8";
const SUCCESS: &str = "{\"code\":0,\"message\":\"ok\",\"data\":[{\"key\":\"bench.account.admin.permissions\",\"component\":\"bench\",\"domain\":\"account\",\"site\":\"admin\",\"action\":\"permissions\",\"method\":\"GET\",\"kind\":\"http\"}]}";
struct HttpReply {
status: u16,
content_type: String,
body: Vec<u8>,
}
struct HttpConnection(BufReader<TcpStream>);
impl HttpConnection {
fn connect(port: u16) -> Self {
let socket = TcpStream::connect(("127.0.0.1", port)).unwrap();
socket.set_nodelay(true).unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
socket
.set_write_timeout(Some(Duration::from_secs(5)))
.unwrap();
Self(BufReader::new(socket))
}
fn request(&mut self, request: &[u8]) -> HttpReply {
self.0.get_mut().write_all(request).unwrap();
let mut line = String::new();
self.0.read_line(&mut line).unwrap();
assert!(line.starts_with("HTTP/1.1 "));
let status = line.split_whitespace().nth(1).unwrap().parse().unwrap();
let mut length = None;
let mut content_type = String::new();
loop {
line.clear();
assert!(self.0.read_line(&mut line).unwrap() > 0);
if line == "\r\n" {
break;
}
let (name, value) = line.trim_end().split_once(':').unwrap();
match name.to_ascii_lowercase().as_str() {
"content-length" => {
assert!(length.is_none());
length = Some(value.trim().parse::<usize>().unwrap());
}
"content-type" => content_type = value.trim().into(),
"connection" => assert_ne!(value.trim(), "close"),
"transfer-encoding" | "set-cookie" => panic!("unexpected response header"),
_ => (),
}
}
let mut body = vec![0; length.unwrap()];
self.0.read_exact(&mut body).unwrap();
HttpReply {
status,
content_type,
body,
}
}
}
fn request_bytes(request: &Request) -> Vec<u8> {
assert!(request.body().is_empty());
let mut bytes = format!("{} {} HTTP/1.1\r\n", request.method(), request.uri()).into_bytes();
for (name, value) in request.headers() {
bytes.extend_from_slice(name.as_str().as_bytes());
bytes.extend_from_slice(b": ");
bytes.extend_from_slice(value.as_bytes());
bytes.extend_from_slice(b"\r\n");
}
bytes.extend_from_slice(b"Connection: keep-alive\r\nContent-Length: 0\r\n\r\n");
bytes
}
fn check_reply(reply: &HttpReply, status: u16) {
assert_eq!(reply.status, status);
assert_eq!(reply.content_type, CONTENT_TYPE);
let expected = if status == 200 {
SUCCESS.as_bytes().to_vec()
} else {
api::error(status).body().to_vec()
};
assert_eq!(reply.body, expected);
}
+123
View File
@@ -0,0 +1,123 @@
// 两侧复用相同 HTTP 引擎、限额和调度策略;计时包含真实 JWT 校验、三次数据库读取及响应。
fn with_server<Value>(
runtime: &Runtime,
registry: &Arc<Registry>,
config: &Arc<api::Config>,
input: &AuthInput,
generated: bool,
client: impl FnOnce(u16) -> Value,
) -> Value {
let registry = registry.clone();
let config = config.clone();
let authentication = input.service.clone();
std::thread::scope(|threads| {
let (started, port) = std::sync::mpsc::sync_channel(0);
let (finished, completion) = tokio::sync::oneshot::channel();
let serving = threads.spawn(move || {
runtime
.run(async move |scope| {
let listener = dever_runtime::net::listen("127.0.0.1", 0, scope)
.await
.unwrap();
let port = listener.port().unwrap() as u16;
let incoming = listener.clone();
let server = scope
.spawn(move |child| async move {
let limits = dever_runtime::http::Limits {
connections: 1,
..Default::default()
};
api::serve::<_, _, true>(
&incoming,
&limits,
&child,
None,
move |request, scope, peer| {
let config = config.clone();
let registry = registry.clone();
let authentication = authentication.clone();
async move {
let path = request.uri().path();
let response = match (request.method().as_str(), path) {
("GET", ROUTE) if config.accepts("admin", &request) => {
if generated {
GENERATED_HTTP(
&request,
peer,
&scope,
&registry,
&authentication,
)
.await
} else {
rust_http(
&request,
peer,
&scope,
&registry,
&authentication,
)
.await
}
}
_ => api::error(404),
};
Ok(response)
}
},
)
.await
})
.unwrap();
// 阻塞客户端独立于运行时线程,也不占用数据库的阻塞池;交接在计时外。
started.send(port).unwrap();
completion.await.unwrap();
listener.close().unwrap();
match server.timeout(5000).await.unwrap() {
dever_runtime::task::Timed::Ready(result) => result.unwrap(),
_ => panic!("owned HTTP benchmark server failed to drain"),
}
})
.unwrap()
});
let result = client(port.recv().unwrap());
finished.send(()).unwrap();
serving.join().unwrap();
result
})
}
pub(super) fn run(
runtime: &Runtime,
registry: &Arc<Registry>,
config: &Arc<api::Config>,
input: &AuthInput,
) {
check(runtime, registry, config, input);
eprintln!(
"HTTP/1.1 loopback; one persistent connection; rotating 16 JWT cookies; JWT + Provider + role gate + permission list + JSON response included; TCP connect and response assertions excluded; no TLS/proxy/concurrent-load claim"
);
let requests = input.requests[..16]
.iter()
.map(request_bytes)
.collect::<Vec<_>>();
samples("http_jwt_provider_roles", |generated, count| {
with_server(runtime, registry, config, input, generated, |port| {
let mut connection = HttpConnection::connect(port);
check_reply(&connection.request(&requests[0]), 200);
let mut replies = Vec::with_capacity(count);
let start = Instant::now();
for index in 0..count {
replies.push(connection.request(black_box(&requests[index % requests.len()])));
}
let elapsed = start.elapsed().as_nanos();
let mut checksum = 0u64;
for reply in &replies {
check_reply(reply, 200);
checksum += black_box(reply.body.iter().map(|byte| u64::from(*byte)).sum::<u64>());
}
(elapsed, checksum)
})
});
check(runtime, registry, config, input);
}
@@ -0,0 +1,102 @@
// 手写 Rust 完成同样的请求边界、Provider、权限门禁和业务返回,不调用生成 API。
use dever_runtime::{api, authorization, wire};
use wire::serde::{self, Deserialize, Serialize};
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct RustPermission {
key: String,
component: String,
domain: String,
site: String,
action: String,
method: String,
kind: String,
}
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct EmptyFields {}
struct EmptyInput;
impl<'input> Deserialize<'input> for EmptyInput {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
let _: EmptyFields = wire::object(decoder)?;
Ok(Self)
}
}
fn access_failure(error: authorization::Failure) -> ApiFailure {
match error {
authorization::Failure::Access(status) => ApiFailure(status),
authorization::Failure::Database(_) => ApiFailure(500),
}
}
async fn rust_http(
request: &Request,
peer: std::net::SocketAddr,
scope: &Scope<'_>,
registry: &Arc<Registry>,
authentication: &Arc<auth::Service>,
) -> Response {
let now = match auth::now() {
Ok(now) => now,
Err(error) => return api::error(error.status()),
};
let (mut context, claims) = match authentication.prepare_at("admin", request, false, now, peer)
{
Ok(prepared) => prepared,
Err(error) => return api::error(error.status()),
};
if let Some(claims) = claims {
if let Err(error) = rust_verify(&mut context, claims, scope, registry).await {
return error.response();
}
}
let mut database = DbContext::new(registry.clone());
let outcome = async {
authorization::authorize(
&mut database,
AUTH_BINDING,
scope,
&context,
"bench",
"account",
"permissions",
)
.await
.map_err(access_failure)?;
let input = api::input(request).map_err(ApiFailure)?;
let _: EmptyInput = input
.decode(dever_runtime::wire::decode)
.map_err(|_| ApiFailure(400))?;
let values = authorization::permissions(&mut database, AUTH_BINDING, scope, &context, true)
.await
.map_err(access_failure)?
.into_iter()
.map(|value| RustPermission {
key: value.key,
component: value.component,
domain: value.domain,
site: value.site,
action: value.action,
method: value.method,
kind: value.kind,
})
.collect::<Vec<_>>();
wire::response(&values).map_err(ApiFailure::from)
}
.await;
match outcome {
Ok(body) => {
let mut response = api::response(body);
context.publish(&mut response);
response
}
Err(error) => error.response(),
}
}
+297
View File
@@ -0,0 +1,297 @@
// JWT 解析在计时前完成;本场景测 Provider 数据库复核及随后实际角色读取。
use dever_runtime::api::{Request, Response};
struct AuthInput {
service: Arc<auth::Service>,
requests: Vec<Request>,
claims: Vec<Claims>,
now: i64,
}
fn prepare(path: &Path) -> (Arc<dever_runtime::api::Config>, AuthInput) {
let config = Arc::new(dever_runtime::api::Config::load(path, &["admin"]).unwrap());
let service = Arc::new(
auth::Service::new(
config.clone(),
&[auth::Verification {
name: "bench.account.verify",
user_model: AUTH_USER_MODEL,
tenant_model: AUTH_TENANT_MODEL,
}],
&[auth::Route {
topic: "admin",
required: true,
user_models: &[AUTH_USER_MODEL],
tenant_models: &[AUTH_TENANT_MODEL],
}],
)
.unwrap(),
);
let now = auth::now().unwrap();
let claims = (0..17)
.map(|index| Claims {
subject: format!("account-{index:02} 中文\""),
session: format!("session-{index}"),
site: "admin".into(),
tenant: None,
})
.collect::<Vec<_>>();
let requests = claims
.iter()
.map(|claims| {
// 只用公开 Cookie 接口准备凭据,不读取 Secret 的私有字节。
let mut bootstrap = Request::new(Default::default());
*bootstrap.method_mut() = "POST".parse().unwrap();
*bootstrap.uri_mut() = "/login".parse().unwrap();
bootstrap
.headers_mut()
.insert("host", "benchmark.test".parse().unwrap());
bootstrap
.headers_mut()
.insert("origin", "https://benchmark.test".parse().unwrap());
let (mut context, _) = service.prepare("admin", &bootstrap, true, now).unwrap();
context
.issue_cookie(&claims.subject, &claims.session, None, now)
.unwrap();
let mut response = Response::new(Default::default());
context.publish(&mut response);
let cookie = response.headers().get("set-cookie").unwrap();
let cookie = std::str::from_utf8(cookie.as_bytes())
.unwrap()
.split(';')
.next()
.unwrap()
.as_bytes()
.to_vec();
let mut request = Request::new(Default::default());
*request.uri_mut() = "/bench/account/admin/permissions".parse().unwrap();
request
.headers_mut()
.insert("host", "benchmark.test".parse().unwrap());
request
.headers_mut()
.insert("cookie", cookie.try_into().unwrap());
request
})
.collect();
(
config,
AuthInput {
service,
requests,
claims,
now,
},
)
}
fn measure_auth(
runtime: &Runtime,
registry: &Arc<Registry>,
input: &AuthInput,
generated: bool,
roles: bool,
count: usize,
) -> (u128, u64) {
runtime
.run(async |scope| {
let mut contexts = input.requests[..16]
.iter()
.map(|request| {
input
.service
.prepare("admin", request, false, input.now)
.unwrap()
.0
})
.collect::<Vec<_>>();
let mut checksum = 0u64;
let start = Instant::now();
for index in 0..count {
let position = index % contexts.len();
let context = &mut contexts[position];
verify(
generated,
context,
black_box(&input.claims[position]).clone(),
scope,
registry,
)
.await
.map_err(|error| error.0)
.unwrap();
checksum += black_box(
context.user_id(None).unwrap().unwrap() as u64
+ context.id().unwrap().len() as u64,
);
if roles {
let mut database = DbContext::new(registry.clone());
let values =
black_box(permissions(generated, context, scope, &mut database).await);
for value in &values {
for field in value {
checksum += field.bytes().map(u64::from).sum::<u64>();
}
}
}
}
(start.elapsed().as_nanos(), checksum)
})
.unwrap()
}
fn check_auth(runtime: &Runtime, registry: &Arc<Registry>, input: &AuthInput) {
runtime
.run(async |scope| {
let expected = vec![
[
"bench.account.admin.permissions",
"bench",
"account",
"admin",
"permissions",
"GET",
"http",
]
.map(String::from),
];
for generated in [true, false] {
for index in 0..16 {
let (mut context, claims) = input
.service
.prepare("admin", &input.requests[index], false, input.now)
.unwrap();
verify(generated, &mut context, claims.unwrap(), scope, registry)
.await
.map_err(|error| error.0)
.unwrap();
assert_eq!(context.id().unwrap(), input.claims[index].subject);
assert_eq!(
context.user_id(Some(AUTH_USER_MODEL)).unwrap(),
Some(index as i64 + 1)
);
assert!(
context
.tenant_id(Some(AUTH_TENANT_MODEL))
.unwrap()
.is_none()
);
let mut database = DbContext::new(registry.clone());
assert_eq!(
permissions(generated, &mut context, scope, &mut database).await,
expected
);
}
for (index, wrong_session) in [(16, false), (0, true)] {
let (mut context, claims) = input
.service
.prepare("admin", &input.requests[index], false, input.now)
.unwrap();
let mut claims = claims.unwrap();
if wrong_session {
claims.session.push_str("-revoked");
}
assert_eq!(
verify(generated, &mut context, claims, scope, registry)
.await
.unwrap_err()
.0,
401
);
assert!(context.id().is_err());
}
}
})
.unwrap();
}
fn main() {
let (directory, path) = directory(
"auth",
r#""http":{"host":"127.0.0.1","port":18001},"sites":{"admin":{"path":"admin","hosts":["benchmark.test"],"auth":"session","origin":"https://benchmark.test"}},"auth":{"providers":{"session":{"verify":"bench.account.verify","jwtSecret":"benchmark-only-key-not-for-any-deployment","ttlSeconds":3600,"cookie":"session"}}}"#,
);
let runtime = runtime();
let registry = runtime
.run(async |scope| database_registry(&path, scope).await)
.unwrap()
.unwrap();
let (config, input) = prepare(&path);
runtime
.run(async |scope| {
let mut database = DbContext::new(registry.clone());
for (index, claims) in input.claims.iter().enumerate() {
let account = GENERATED_SEED(
&claims.subject,
&claims.session,
index < 16,
scope,
&mut database,
)
.await
.unwrap();
assert_eq!(account.field0.0, index as i64 + 1);
}
let (mut context, claims) = input
.service
.prepare("admin", &input.requests[0], false, input.now)
.unwrap();
drop(database);
api_verify(&mut context, claims.unwrap(), scope, &registry)
.await
.map_err(|error| error.0)
.unwrap();
let mut database = DbContext::new(registry.clone());
let executor = database.on(AUTH_BINDING, scope).await.unwrap();
dever_runtime::authorization::synchronize(
executor,
scope,
&config,
&[dever_runtime::authorization::Declaration {
topic: "admin",
component: "bench",
domain: "account",
action: "permissions",
method: "GET",
}],
)
.await
.unwrap();
dever_runtime::authorization::save(
&mut database,
AUTH_BINDING,
scope,
&context,
"reader",
"Benchmark reader",
false,
&["bench.account.admin.permissions".into()],
)
.await
.unwrap();
for user in 1..=16 {
let executor = database.on(AUTH_BINDING, scope).await.unwrap();
dever_runtime::authorization::grant(executor, scope, &context, user, "reader")
.await
.unwrap();
}
})
.unwrap();
check_auth(&runtime, &registry, &input);
header();
if HTTP_BENCH {
protected_http::run(&runtime, &registry, &config, &input);
} else {
for (name, roles) in [
("auth_provider_verify", false),
("auth_provider_role_read", true),
] {
samples(name, |generated, count| {
measure_auth(&runtime, &registry, &input, generated, roles, count)
});
}
}
check_auth(&runtime, &registry, &input);
drop(registry);
drop(runtime);
drop(directory);
}
+114
View File
@@ -0,0 +1,114 @@
// 手写 Rust 重做 Provider 的查询、完整行解码和身份判断;不调用生成函数。
use dever_runtime::{
api::Failure as ApiFailure,
auth::{self, Claims, Identity},
};
struct RustAccount {
id: dever_runtime::database::Id<AUTH_USER_MODEL>,
_created_at: dever_runtime::time::calendar::DateTime,
subject: String,
_session: String,
_active: bool,
}
async fn rust_verify(
context: &mut auth::Context<'_>,
claims: Claims,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> Result<(), ApiFailure> {
if context.verifier() != Some("bench.account.verify") {
return Err(ApiFailure(500));
}
let mut database = DbContext::new(registry.clone());
let executor = database
.on(ACCOUNT_BINDING, scope)
.await
.map_err(|_| ApiFailure(500))?;
let mut rows = executor.query(scope,
"SELECT \"id\",\"created_at\",\"subject\",\"session\",\"active\" FROM \"bench_account_model\" WHERE (((\"subject\" IS ?1) AND (\"session\" IS ?2)) AND (\"active\" IS ?3)) ORDER BY \"id\" DESC LIMIT 1",
vec![Parameter::Text(claims.subject), Parameter::Text(claims.session), Parameter::Integer(1)], 1,
|row| Ok(RustAccount {
id: dever_runtime::database::Id(row.get::<_, i64>(0)?),
_created_at: dever_runtime::time::calendar::DateTime::from_millis(row.get::<_, i64>(1)?).map_err(|_| dever_runtime::database::Error::new(dever_runtime::database::Kind::InvalidData, "invalid stored DateTime"))?,
subject: row.get(2)?, _session: row.get(3)?, _active: row.get(4)?,
})
).await.map_err(|_| ApiFailure(500))?;
let row = rows.pop().ok_or(ApiFailure(401))?;
context
.establish(Identity {
id: row.subject,
user_id: Some(row.id.0),
tenant_id: None,
})
.map_err(|error| ApiFailure(error.status()))
}
// 输出所有字段,防止只读长度让优化器删掉 Dever 或 Rust 的记录转换。
type PermissionView = [String; 7];
async fn rust_permissions(
context: &mut auth::Context<'_>,
scope: &Scope<'_>,
database: &mut DbContext,
) -> Vec<PermissionView> {
dever_runtime::authorization::permissions(database, AUTH_BINDING, scope, context, true)
.await
.unwrap()
.into_iter()
.map(|value| {
[
value.key,
value.component,
value.domain,
value.site,
value.action,
value.method,
value.kind,
]
})
.collect()
}
async fn permissions(
generated: bool,
context: &mut auth::Context<'_>,
scope: &Scope<'_>,
database: &mut DbContext,
) -> Vec<PermissionView> {
if generated {
GENERATED_PERMISSIONS(scope, database, context)
.await
.unwrap()
.into_iter()
.map(|value| {
[
value.field0,
value.field1,
value.field2,
value.field3,
value.field4,
value.field5,
value.field6,
]
})
.collect()
} else {
rust_permissions(context, scope, database).await
}
}
async fn verify(
generated: bool,
context: &mut auth::Context<'_>,
claims: Claims,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> Result<(), ApiFailure> {
if generated {
api_verify(context, claims, scope, registry).await
} else {
rust_verify(context, claims, scope, registry).await
}
}
+92
View File
@@ -0,0 +1,92 @@
// SQLite、资源作用域和优化参数两侧一致;只比较生成层,不推断完整语言或 HTTP 吞吐。
use dever_runtime::{
database::{Context as DbContext, Parameter, Registry},
task::{Limits, Runtime, Scope},
};
use std::{
hint::black_box,
path::{Path, PathBuf},
sync::Arc,
time::Instant,
};
struct BenchDirectory(PathBuf);
impl Drop for BenchDirectory {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.0).unwrap();
}
}
fn directory(name: &str, extra: &str) -> (BenchDirectory, PathBuf) {
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let relative = format!("{name}-benchmark-{}-{unique}", std::process::id());
let directory = BenchDirectory(
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.join(&relative),
);
std::fs::create_dir(&directory.0).unwrap();
let settings = directory.0.join("setting.json");
std::fs::write(&settings, format!(r#"{{"database":{{"default":{{"type":"sqlite","path":"{relative}/case.db","max_connections":1,"max_page_size":2048}}}},{extra}}}"#)).unwrap();
(directory, settings)
}
fn runtime() -> Runtime {
Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 16,
})
.unwrap()
}
fn smoke() -> bool {
std::env::args().any(|value| value == "--smoke")
}
fn samples(name: &str, mut measure: impl FnMut(bool, usize) -> (u128, u64)) {
if let Ok(selected) = std::env::var("DEVER_BENCH_CASES")
&& !selected.split(',').any(|case| case == name)
{
return;
}
let (rounds, count) = if smoke() { (1, 4) } else { (15, 2000) };
if !smoke() {
for generated in [true, false] {
black_box(measure(generated, 200));
}
}
for sample in 0..rounds {
let mut order = [("dever", true), ("rust", false), ("control", true)];
order.rotate_left(sample % 3);
let mut expected = None;
for (implementation, generated) in order {
let (elapsed, checksum) = measure(generated, count);
if let Some(expected) = expected {
assert_eq!(checksum, expected);
}
expected = Some(checksum);
if !smoke() {
println!("{name},{sample},{implementation},{elapsed},{checksum}");
}
}
}
if smoke() {
println!("correctness smoke passed: {name}; no performance result collected");
}
}
fn header() {
eprintln!(
"Rust 1.95.0; release fat LTO; codegen-units=1; overflow-checks=true; SQLite WAL/NORMAL; one connection; one runtime worker; setup/checks excluded"
);
if !smoke() {
println!("case,sample,implementation,nanoseconds,checksum");
}
}
@@ -0,0 +1,73 @@
//! 文本和集合对照的构建入口,与其他性能组共用正式生成器和 Cargo 配置。
use super::compare;
use std::path::Path;
pub(crate) fn run(core: &Path) -> bool {
if std::env::args().any(|argument| argument == "--text") {
compare(
core,
"text",
"text",
&(include_str!("../samples.rs").to_owned() + include_str!("../text.rs")),
&[
("GENERATED_CHAIN", "chain"),
("GENERATED_PAIR", "pair"),
("GENERATED_TEMPORARY", "temporarySuffix"),
("GENERATED_FORWARDED", "forwardedSuffix"),
("GENERATED_CONTAINS", "containsAll"),
("GENERATED_LIST_CONTAINS", "listContains"),
("GENERATED_TEXT_CONCAT", "concatenateText"),
("GENERATED_BYTES_CONCAT", "concatenateBytes"),
],
&[("SEARCH_PAIR", "SearchPair")],
);
return true;
}
if std::env::args().any(|argument| argument == "--map-values") {
compare(
core,
"map-values",
"map-values",
&(include_str!("../samples.rs").to_owned()
+ include_str!("../map_values/mod.rs")
+ include_str!("../map_values/select.rs")),
&[
("GENERATED_BUILD_TEXT", "buildText"),
("GENERATED_BUILD_NESTED", "buildNested"),
("GENERATED_OWNED_TEXT", "ownedText"),
("GENERATED_OWNED_NESTED", "ownedNested"),
("GENERATED_BORROWED_TEXT", "borrowedText"),
("GENERATED_BORROWED_NESTED", "borrowedNested"),
("GENERATED_LIST_FIRST", "firstText"),
("GENERATED_MAP_GET", "lookupText"),
("GENERATED_MAP_FILTER", "filterText"),
("GENERATED_MAP_FIND", "findText"),
],
&[],
);
return true;
}
if std::env::args().any(|argument| argument == "--storage") {
compare(
core,
"storage",
"storage",
&(include_str!("../samples.rs").to_owned() + include_str!("../storage.rs")),
&[
("GENERATED_LOOKUP", "lookup"),
("GENERATED_SCAN", "scan"),
("GENERATED_BUILD_MAP", "buildMap"),
("GENERATED_ENTRIES", "buildEntries"),
("GENERATED_BUILD_LIST", "buildList"),
("GENERATED_BUFFER_BUILD", "bufferBuild"),
("GENERATED_BUFFER_INSPECT", "bufferInspect"),
("GENERATED_RECORD_READ", "readScalarRows"),
("GENERATED_TEXT_LOOKUP", "lookupText"),
],
&[("BUFFER_STATE", "BufferState"), ("SCALAR_ROW", "ScalarRow")],
);
return true;
}
false
}
+186
View File
@@ -0,0 +1,186 @@
//! 所有性能组共用正式生成器、锁定依赖和 release 构建参数。
use crate::auth_build;
mod collections;
pub(crate) use collections::run as collections;
use dever::compiler::{emit, project::Project};
use dever::process::{child::output::require_success, command};
use std::path::Path;
pub(crate) fn compare(
core: &Path,
fixture: &str,
name: &str,
reference: &str,
functions: &[(&str, &str)],
definitions: &[(&str, &str)],
) {
let path = core.join(format!("test/rust/fixtures/{fixture}"));
let path = if path.is_dir() {
path
} else {
path.with_extension("dever")
};
let project = Project::load(&path).unwrap();
let program = project.check().unwrap();
let mut comparison = auth_build::metadata(name, &program, reference);
if name == "decimal-extra" {
for (marker, path) in [
("DECIMAL_CODEC_SOURCE", "codec/numeric.rs"),
("DECIMAL_SCALAR_SOURCE", "scalar/decimal.rs"),
] {
comparison = comparison.replace(
&format!("\"{marker}\""),
&format!("{:?}", core.join("rust/src/runtime/database").join(path)),
);
}
}
let memory = std::env::args().any(|argument| argument == "--memory");
if memory {
assert!(
comparison.contains("fn compare_output<"),
"memory requires the shared sample harness"
);
comparison = comparison.replacen("fn compare_output<", "fn timed_compare_output<", 1);
comparison.push_str(include_str!("../memory.rs"));
}
let mut roots = Vec::new();
for (marker, name) in functions {
if let Some(index) = program
.commands
.iter()
.position(|command| command.name == *name)
{
comparison = comparison.replace(marker, &format!("command{index}"));
roots.push(program.commands[index].function);
continue;
}
let (package, name) = name
.rsplit_once('.')
.map_or((None, *name), |(package, name)| (Some(package), name));
let position = program
.functions
.iter()
.position(|function| {
function.signature.name == name
&& package.is_none_or(|package| {
program.packages[function.signature.source] == package
})
})
.unwrap();
comparison = comparison.replace(marker, &format!("function{position}"));
roots.push(position);
}
for (marker, name) in definitions {
let position = program
.definitions
.iter()
.position(|definition| definition.name == *name)
.unwrap();
comparison = comparison.replace(marker, &format!("Type{position}"));
}
let task_dependencies = if name == "rest-json" {
"chrono = { version = \"=0.4.45\", default-features = false, features = [\"std\"] }\n"
} else if name == "tasks" {
"tokio = { version = \"=1.53.1\", features = [\"rt-multi-thread\", \"sync\", \"time\", \"macros\"] }\ntokio-util = { version = \"=0.7.19\", features = [\"rt\"] }\nfutures-util = { version = \"=0.3.34\", default-features = false, features = [\"std\"] }\n"
} else if matches!(name, "websocket" | "websocket-protocol") {
"tokio = { version = \"=1.53.1\", features = [\"rt-multi-thread\", \"net\", \"sync\", \"time\", \"macros\"] }\ntokio-util = { version = \"=0.7.19\", features = [\"rt\"] }\nfutures-util = { version = \"=0.3.34\", default-features = false, features = [\"std\", \"sink\"] }\ntokio-tungstenite = { version = \"=0.30.0\", default-features = false, features = [\"handshake\"] }\n"
} else if name == "query" {
"axum = { version = \"=0.8.9\", default-features = false, features = [\"query\"] }\nserde = { version = \"=1.0.228\", features = [\"derive\"] }\nserde_urlencoded = \"=0.7.1\"\n"
} else if name == "transport" {
crate::transport::DEPENDENCIES
} else if matches!(name, "auth" | "auth-http") {
"tokio = { version = \"=1.53.1\", features = [\"sync\"] }\n"
} else if name == "extended-primitives" {
"sha2 = \"=0.10.9\"\nuuid = { version = \"=1.28.0\", default-features = false }\n"
} else if name == "decimal-extra" {
"serde = \"=1.0.228\"\nserde_json = \"=1.0.145\"\nbytes = \"=1.12.1\"\n"
} else if name == "api-wire" {
"serde_json = \"=1.0.145\"\nhyper = \"=1.11.1\"\n"
} else if matches!(name, "database" | "database-postgres" | "application") {
"serde_json = \"=1.0.145\"\n"
} else {
""
};
let name = if memory {
format!("{name}-memory")
} else {
name.to_owned()
};
let directory = core.join(format!("rust/target/performance/{name}"));
std::fs::create_dir_all(&directory).unwrap();
roots.extend(program.commands.iter().map(|command| command.function));
if matches!(
name.trim_end_matches("-memory"),
"auth" | "auth-http" | "jobs" | "api-wire" | "rest-wire" | "rest-json"
) {
roots.extend(program.entries());
}
let roots = roots.into_iter().collect::<std::collections::BTreeSet<_>>();
let functions = program.reachable(roots.iter().copied());
let mut source = emit::generate(&program, &project.sources, &roots);
source.push_str(&auth_build::application(
name.trim_end_matches("-memory"),
&program,
));
source.push_str(&comparison);
write(&directory.join("main.rs"), &source);
let mut features = dever::compiler::build::runtime_features(&program, &functions);
if name.trim_end_matches("-memory") == "query" {
features.push("api");
}
if matches!(
name.trim_end_matches("-memory"),
"database-postgres" | "decimal-extra"
) {
features.push("postgres");
}
let features = format!(", features = {features:?}");
write(
&directory.join("Cargo.toml"),
&format!(
"[package]\nname = \"dever-comparison-{name}\"\nversion = \"0.0.1\"\nedition = \"2024\"\n[workspace]\n[[bin]]\nname = \"dever-comparison-{name}\"\npath = \"main.rs\"\n[dependencies]\ndever-runtime = {{ path = {:?}{features} }}\nindexmap = \"=2.14.0\"\ndec = \"=0.4.11\"\n{task_dependencies}[profile.release]\nlto = \"fat\"\ncodegen-units = 1\noverflow-checks = true\n",
core.join("rust/src/runtime").to_string_lossy()
),
);
// 基准清单随候选运行库变化;只刷新这个生成项目的离线依赖锁。
let arguments = ["+1.95.0", "generate-lockfile", "--offline"];
let child = command::start("cargo".as_ref(), &arguments, &directory, true).unwrap();
require_success(child.output().unwrap()).unwrap();
let result = command::start(
"cargo".as_ref(),
&[
"+1.95.0",
"build",
"--offline",
"--locked",
"--release",
"--target-dir",
"../../programs",
],
&directory,
true,
)
.unwrap()
.output()
.unwrap();
require_success(result).unwrap();
let binary = core.join(format!(
"rust/target/programs/release/dever-comparison-{name}"
));
if memory || std::env::args().any(|argument| argument == "--build-only") {
println!("{}", binary.display());
return;
}
let status = command::start(binary.as_os_str(), &[] as &[&str], &directory, false)
.unwrap()
.wait()
.unwrap();
assert!(status.success());
}
fn write(path: &Path, contents: &str) {
if std::fs::read_to_string(path).ok().as_deref() != Some(contents) {
std::fs::write(path, contents).unwrap();
}
}
+170
View File
@@ -0,0 +1,170 @@
fn byte_fault() -> Fault {
&FaultInfo {
message: "byte conversion failed",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn total_reference(values: &dever_runtime::Bytes) -> Result<i64, Fault> {
values.iter().try_fold(0i64, |total, value| {
total.checked_add(i64::from(*value)).ok_or_else(byte_fault)
})
}
fn each_reference(values: &dever_runtime::Bytes) -> Result<Vec<i64>, Fault> {
values
.iter()
.map(|value| i64::from(*value).checked_add(1).ok_or_else(byte_fault))
.collect()
}
fn encode_generated(values: &[String]) -> Result<dever_runtime::Bytes, Fault> {
GENERATED_ENCODE(values.first().map(String::as_str).unwrap_or(""))
}
fn encode_reference(values: &[String]) -> Result<dever_runtime::Bytes, Fault> {
Ok(values
.first()
.map(String::as_str)
.unwrap_or("")
.as_bytes()
.to_vec()
.into())
}
fn decode_generated(values: &dever_runtime::Bytes) -> Result<String, Fault> {
GENERATED_DECODE(values).map_err(|_| byte_fault())
}
fn decode_reference(values: &dever_runtime::Bytes) -> Result<String, Fault> {
std::str::from_utf8(values)
.map(str::to_owned)
.map_err(|_| byte_fault())
}
fn convert_generated(values: &[i64]) -> Result<dever_runtime::Bytes, Fault> {
GENERATED_CONVERT(values).map_err(|_| byte_fault())
}
fn convert_reference(values: &[i64]) -> Result<dever_runtime::Bytes, Fault> {
values
.iter()
.map(|value| u8::try_from(*value).map_err(|_| byte_fault()))
.collect::<Result<Vec<_>, _>>()
.map(Into::into)
}
fn slice_generated(values: &dever_runtime::Bytes) -> Result<dever_runtime::Bytes, Fault> {
GENERATED_SLICE(values).map_err(|_| byte_fault())
}
fn slice_reference(values: &dever_runtime::Bytes) -> Result<dever_runtime::Bytes, Fault> {
values
.get(0..values.len())
.map(<[u8]>::to_vec)
.map(Into::into)
.ok_or_else(byte_fault)
}
fn join_reference(values: &dever_runtime::Bytes) -> Result<dever_runtime::Bytes, Fault> {
let mut result = Vec::with_capacity(values.len() + values.len());
result.extend_from_slice(values);
result.extend_from_slice(values);
Ok(result.into())
}
fn roundtrip_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_ROUNDTRIP(values.first().map(String::as_str).unwrap_or("")).map_err(|_| byte_fault())
}
fn roundtrip_reference(values: &[String]) -> Result<String, Fault> {
let bytes = values
.first()
.map(String::as_str)
.unwrap_or("")
.as_bytes()
.to_vec();
String::from_utf8(bytes).map_err(|_| byte_fault())
}
fn main() {
let text = "hello 雪🙂 ".repeat(256);
let texts = [text];
let bytes = dever_runtime::Bytes::copy_from_slice(texts[0].as_bytes());
let integers: Vec<i64> = (0..1024).map(|value| value % 256).collect();
let invalid = dever_runtime::Bytes::from_static(&[255]);
assert!(decode_generated(&invalid).is_err());
assert!(decode_reference(&invalid).is_err());
assert_eq!(GENERATED_IDENTITY(&bytes).unwrap().as_ptr(), bytes.as_ptr());
assert!(convert_generated(&[-1]).is_err());
assert!(convert_reference(&[-1]).is_err());
assert!(convert_generated(&[256]).is_err());
assert!(convert_reference(&[256]).is_err());
println!("case,sample,implementation,elapsed_ns,checksum");
compare_bytes(
"bytes_reduce",
&bytes,
2000,
GENERATED_TOTAL,
total_reference,
|value| *value as u64,
);
compare_bytes(
"bytes_each",
&bytes,
2000,
GENERATED_EACH,
each_reference,
|value| value.len() as u64,
);
compare(
"bytes_encode",
&texts,
20000,
encode_generated,
encode_reference,
|value| value.len() as u64,
);
compare_bytes(
"bytes_decode",
&bytes,
4000,
decode_generated,
decode_reference,
|value| value.len() as u64,
);
compare(
"bytes_from_ints",
&integers,
2000,
convert_generated,
convert_reference,
|value| value.len() as u64,
);
compare_bytes(
"bytes_slice",
&bytes,
20000,
slice_generated,
slice_reference,
|value| value.len() as u64,
);
compare_bytes(
"bytes_concat",
&bytes,
10000,
GENERATED_JOIN,
join_reference,
|value| value.len() as u64,
);
compare(
"bytes_roundtrip",
&texts,
4000,
roundtrip_generated,
roundtrip_reference,
|value| value.len() as u64,
);
}
+231
View File
@@ -0,0 +1,231 @@
use std::hint::black_box;
use std::time::Instant;
#[derive(Debug)]
enum ReferenceFailure {
Fault(Fault),
Failed(String, Fault),
}
fn state_reference(value: &STATE_TYPE) -> Result<i64, Fault> {
Ok(match value {
STATE_TYPE::Variant0 => 0,
STATE_TYPE::Variant1(number) => *number,
STATE_TYPE::Variant2(text) => text.chars().count() as i64,
})
}
fn nullable_reference(value: &Option<String>) -> Result<i64, Fault> {
Ok(match value {
None => -1,
Some(text) if text == "zero" => 0,
Some(text) => text.chars().count() as i64,
})
}
fn operation_reference(value: i64) -> Result<i64, ReferenceFailure> {
if value >= 0 {
Ok(value)
} else {
Err(ReferenceFailure::Failed(
"negative".into(),
&FaultInfo {
message: "Result.Failed",
source: "reference.dever",
line: 1,
column: 1,
},
))
}
}
fn propagate_reference(value: i64) -> Result<i64, ReferenceFailure> {
Ok(operation_reference(value)?)
}
fn capture_reference(value: i64) -> Result<RESULT_TYPE, Fault> {
match propagate_reference(value) {
Ok(number) => Ok(RESULT_TYPE::Variant0(number)),
Err(ReferenceFailure::Failed(message, _origin)) => Ok(RESULT_TYPE::Variant1(message)),
Err(ReferenceFailure::Fault(fault)) => Err(fault),
}
}
fn change_reference(value: &STATE_TYPE) -> Result<STATE_TYPE, Fault> {
Ok(match value {
STATE_TYPE::Variant2(text) => {
let mut result = String::with_capacity(text.len() + "新".len());
result.push_str(text);
result.push_str("新");
STATE_TYPE::Variant2(result)
}
_ => value.clone(),
})
}
fn score(value: RESULT_TYPE) -> usize {
match value {
RESULT_TYPE::Variant0(number) => number as usize,
RESULT_TYPE::Variant1(message) => message.len(),
}
}
fn sample(
name: &str,
sample: usize,
implementation: &str,
count: usize,
mut operation: impl FnMut(usize) -> usize,
) {
let start = Instant::now();
let mut checksum = 0usize;
for position in 0..count {
checksum = checksum.wrapping_add(black_box(operation(black_box(position))));
}
println!(
"{name},{sample},{implementation},{},{checksum}",
start.elapsed().as_nanos()
);
}
fn main() {
let states = [
STATE_TYPE::Variant0,
STATE_TYPE::Variant1(42),
STATE_TYPE::Variant2("雪山树林".repeat(16)),
];
let texts = [None, Some("zero".into()), Some("雪山树林".repeat(16))];
for state in &states {
assert_eq!(
GENERATED_STATE(state).unwrap(),
state_reference(state).unwrap()
);
assert_eq!(
GENERATED_CHANGE(state).unwrap(),
change_reference(state).unwrap()
);
}
for text in &texts {
assert_eq!(
GENERATED_NULLABLE(text).unwrap(),
nullable_reference(text).unwrap()
);
}
for value in [i64::MIN, -1, 0, 1, i64::MAX] {
assert_eq!(
GENERATED_PROPAGATE(value).map_err(|_| ()),
propagate_reference(value).map_err(|_| ())
);
assert_eq!(
GENERATED_CAPTURE(value).unwrap(),
capture_reference(value).unwrap()
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
for iteration in 0..9 {
for generated in if iteration % 2 == 0 {
[true, false]
} else {
[false, true]
} {
let implementation = if generated { "dever" } else { "rust" };
if generated {
sample(
"choice_payload",
iteration,
implementation,
1_000_000,
|position| GENERATED_STATE(black_box(&states[position % 3])).unwrap() as usize,
);
sample(
"nullable_text",
iteration,
implementation,
1_000_000,
|position| {
GENERATED_NULLABLE(black_box(&texts[position % 3])).unwrap() as usize
},
);
sample(
"default_failure",
iteration,
implementation,
500_000,
|position| {
black_box(GENERATED_PROPAGATE((position % 8) as i64 - 1)).unwrap_or(7)
as usize
},
);
sample(
"capture_failure",
iteration,
implementation,
500_000,
|position| {
score(black_box(
GENERATED_CAPTURE((position % 8) as i64 - 1).unwrap(),
))
},
);
sample(
"choice_copy",
iteration,
implementation,
300_000,
|_| match black_box(GENERATED_CHANGE(black_box(&states[2])).unwrap()) {
STATE_TYPE::Variant2(text) => text.len(),
_ => unreachable!(),
},
);
} else {
sample(
"choice_payload",
iteration,
implementation,
1_000_000,
|position| state_reference(black_box(&states[position % 3])).unwrap() as usize,
);
sample(
"nullable_text",
iteration,
implementation,
1_000_000,
|position| {
nullable_reference(black_box(&texts[position % 3])).unwrap() as usize
},
);
sample(
"default_failure",
iteration,
implementation,
500_000,
|position| {
black_box(propagate_reference((position % 8) as i64 - 1)).unwrap_or(7)
as usize
},
);
sample(
"capture_failure",
iteration,
implementation,
500_000,
|position| {
score(black_box(
capture_reference((position % 8) as i64 - 1).unwrap(),
))
},
);
sample(
"choice_copy",
iteration,
implementation,
300_000,
|_| match black_box(change_reference(black_box(&states[2])).unwrap()) {
STATE_TYPE::Variant2(text) => text.len(),
_ => unreachable!(),
},
);
}
}
}
}
+161
View File
@@ -0,0 +1,161 @@
fn fault() -> Fault {
&FaultInfo {
message: "integer arithmetic fault",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn map_reference(values: &[i64]) -> Result<Vec<i64>, Fault> {
let mut output = Vec::with_capacity(values.len());
for value in values {
output.push(value.checked_mul(2).ok_or_else(fault)?);
}
Ok(output)
}
fn filter_reference(values: &[i64]) -> Result<Vec<i64>, Fault> {
let mut output = Vec::new();
for value in values {
if *value > 0 {
output.push(*value);
}
}
Ok(output)
}
fn find_reference(values: &[i64]) -> Result<Option<i64>, Fault> {
Ok(values.iter().find(|value| **value > 997).copied())
}
fn find_generated(values: &[i64]) -> Result<Option<i64>, Fault> {
GENERATED_FIND(values, 997)
}
fn sum_reference(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let projected = value.checked_mul(2).ok_or_else(fault)?;
total = total.checked_add(projected).ok_or_else(fault)?;
}
Ok(total)
}
fn fold_reference(values: &[i64]) -> Result<i64, Fault> {
let mut state = 0i64;
for value in values {
state = state.checked_add(*value).ok_or_else(fault)?;
}
Ok(state)
}
fn stop_reference(values: &[i64]) -> Result<i64, Fault> {
let mut state = 0i64;
for value in values {
state = state.checked_add(*value).ok_or_else(fault)?;
if state >= 3 {
break;
}
}
Ok(state)
}
fn rows_reference(values: &[ROW_TYPE]) -> Result<Vec<ROW_TYPE>, Fault> {
let mut output = Vec::with_capacity(values.len());
for value in values {
let mut name = String::with_capacity(value.field0.len() + "新".len());
name.push_str(&value.field0);
name.push_str("新");
output.push(ROW_TYPE {
field0: name,
field1: value.field1.checked_add(1).ok_or_else(fault)?,
});
}
Ok(output)
}
fn main() {
let values: Vec<i64> = (0..1024).map(|value| (value * 17) % 1000 - 1).collect();
let rows: Vec<_> = (0..64)
.map(|index| ROW_TYPE {
field0: "雪山树林".repeat(4),
field1: index,
})
.collect();
for values in [&[i64::MAX][..], &[i64::MAX / 2, 3][..], &[i64::MIN, -1][..]] {
assert_eq!(
GENERATED_MAP(values).map_err(|_| ()),
map_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_SUM(values).map_err(|_| ()),
sum_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_FOLD(values).map_err(|_| ()),
fold_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_STOP(values).map_err(|_| ()),
stop_reference(values).map_err(|_| ())
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"each_int",
&values,
20000,
GENERATED_MAP,
map_reference,
|value| value.len() as u64,
);
compare(
"filter_int",
&values,
20000,
GENERATED_FILTER,
filter_reference,
|value| value.len() as u64,
);
compare(
"find_int",
&values,
50000,
find_generated,
find_reference,
|value| value.unwrap_or(0) as u64,
);
compare(
"project_sum",
&values,
30000,
GENERATED_SUM,
sum_reference,
|value| *value as u64,
);
compare(
"reduce_int",
&values,
30000,
GENERATED_FOLD,
fold_reference,
|value| *value as u64,
);
compare(
"reduce_until_int",
&values,
500000,
GENERATED_STOP,
stop_reference,
|value| *value as u64,
);
compare(
"each_record",
&rows,
5000,
GENERATED_ROWS,
rows_reference,
|value| value.len() as u64,
);
}
+262
View File
@@ -0,0 +1,262 @@
//! 同一负载分别链接改造前后源码;衡量运行库和编译器成本,不冒充独立 Rust 基准。
use bytes::{Bytes, BytesMut};
use dever::{
compiler::check,
syntax::{self, source::Source},
};
use dever_runtime::{
database::Parameter,
task::{Limits, Runtime},
value::decimal::Decimal,
};
use std::{hint::black_box, time::Instant};
use tokio_postgres::types::{ToSql, Type};
fn measure(name: &str, count: usize, mut operation: impl FnMut() -> u64) {
let expected = operation();
for _ in 0..10 {
assert_eq!(operation(), expected);
}
for sample in 0..15 {
let started = Instant::now();
let mut checksum = 0;
for _ in 0..count {
checksum += black_box(operation());
}
let elapsed = started.elapsed().as_nanos();
assert_eq!(checksum, expected * count as u64);
println!("{name},{sample},{count},{elapsed},{checksum}");
}
}
fn numeric() {
for native in [false, true] {
let values = [
"0",
"-12.345",
"1234567890123456789012345.123456789",
"0.000000001",
]
.map(|text| {
let value = Decimal::parse(text).unwrap();
if native {
Parameter::NativeDecimal(value)
} else {
Parameter::Decimal {
value,
precision: 34,
scale: 9,
}
}
});
let mut output = BytesMut::with_capacity(128);
measure(
if native {
"numeric_native"
} else {
"numeric_column"
},
100_000,
|| {
output.clear();
for value in black_box(&values) {
value.to_sql(&Type::NUMERIC, &mut output).unwrap();
}
output.iter().map(|value| u64::from(*value)).sum()
},
);
}
}
fn frontend() {
for count in [100, 1000, 3000] {
let text = (0..count)
.map(|index| format!("function{index}() (answer: Int) {{ answer = {index} }}\n"))
.collect();
let source = Source::new(0, "case.dever", text);
let module = syntax::parse(&source).unwrap();
measure(&format!("function_check_{count}"), 3, || {
check::check(vec![black_box(module.clone())], &["main".into()])
.unwrap()
.functions
.len() as u64
});
}
}
fn command() {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
let executable = std::env::current_exe().unwrap();
let directory = executable.parent().unwrap();
measure("command_exit", 20, || {
runtime.block_on(async {
let mut child =
process_worker::Child::command(&executable, &["--child".into()], directory, "")
.await
.unwrap();
assert!(child.wait().await.unwrap().success());
1
})
});
}
fn upload() {
use dever_runtime::{api::Request, upload};
use http_body_util::Full;
let directory = std::env::temp_dir().join(format!("dever-cost-upload-{}", std::process::id()));
std::fs::create_dir(&directory).unwrap();
let mut request = Request::new(Default::default());
*request.method_mut() = "POST".parse().unwrap();
*request.uri_mut() = "/upload".parse().unwrap();
request.headers_mut().insert(
"content-type",
"multipart/form-data; boundary=boundary".parse().unwrap(),
);
let mut body = b"--boundary\r\nContent-Disposition: form-data; name=file; filename=test.bin\r\nContent-Type: application/octet-stream\r\n\r\n".to_vec();
body.extend(vec![b'x'; 1_048_576]);
body.extend_from_slice(b"\r\n--boundary--\r\n");
let body = Bytes::from(body);
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 32,
})
.unwrap();
measure("upload_1mib", 3, || {
runtime
.run(async |scope| {
let mut multipart = upload::read(
&request,
Full::new(body.clone()),
&directory,
&upload::Limits::default(),
scope,
)
.await
.unwrap();
let file = multipart.take("file").unwrap();
assert_eq!(file.size(), 1_048_576);
multipart.finish().unwrap();
file.close();
1_048_576
})
.unwrap()
});
assert_eq!(std::fs::read_dir(directory.join("tmp")).unwrap().count(), 0);
std::fs::remove_dir_all(directory).unwrap();
}
fn main() {
if std::env::args().any(|value| value == "--child") {
return;
}
println!("case,sample,iterations,elapsed_ns,checksum");
if std::env::args().any(|value| value == "--warm-build") {
warm_build();
return;
}
numeric();
frontend();
command();
upload();
query();
sites();
}
fn warm_build() {
use dever::compiler::build;
let directory = std::env::temp_dir().join(format!("dever-cost-build-{}", std::process::id()));
std::fs::create_dir(&directory).unwrap();
let mut random = 0x9e3779b9u32;
let text: String = (0..8 * 1024 * 1024)
.map(|_| {
random ^= random << 13;
random ^= random >> 17;
random ^= random << 5;
char::from(b'a' + (random % 26) as u8)
})
.collect();
let source = Source::new(
0,
"warm.dever",
format!("main() (answer: Text) {{ answer = {text:?} }}"),
);
let module = syntax::parse(&source).unwrap();
let program = check::check(vec![module], &["main".into()]).unwrap();
let sources = [source];
let initial = directory.join("initial");
build::build(&program, &sources, "main", &initial).unwrap();
assert!(std::fs::metadata(&initial).unwrap().len() > 8 * 1024 * 1024);
let output = std::process::Command::new(&initial).output().unwrap();
assert!(output.status.success());
assert!(output.stdout == format!("{text:?}\n").as_bytes());
let mut next = 0;
measure("warm_build_8mib", 1, || {
let output = directory.join(format!("copy-{next}"));
next += 1;
build::build(&program, &sources, "main", &output).unwrap();
assert!(std::fs::metadata(&output).unwrap().len() > 8 * 1024 * 1024);
std::fs::remove_file(output).unwrap();
1
});
std::fs::remove_dir_all(directory).unwrap();
}
fn query() {
use dever_runtime::api::{self, Request};
let names = (0..32)
.map(|index| format!("field{index}"))
.collect::<Vec<_>>();
// 原 128 次重复超出 Hyper 的 URI 长度上限;本场景只测可接收的请求。
let text = "value%2B%E9%9B%AA".repeat(96);
let query = names
.iter()
.map(|name| format!("{name}={text}"))
.collect::<Vec<_>>()
.join("&");
let mut request = Request::new(Default::default());
*request.uri_mut() = format!("/test?{query}").parse().unwrap();
measure("query_32_fields_96_values", 100, || {
let input = api::input(&request).unwrap();
let values: std::collections::BTreeMap<String, String> = INPUT_DECODE(input).unwrap();
assert_eq!(values["field0"], "value+雪".repeat(96));
values.values().map(|value| value.len() as u64).sum()
});
}
fn sites() {
use dever_runtime::{api::Config, api::Request};
let names = (0..512)
.map(|index| format!("site{index:04}"))
.collect::<Vec<_>>();
let sites = names
.iter()
.map(|name| {
(
name.clone(),
serde_json::json!({"path":name,"hosts":["fixture.test"]}),
)
})
.collect::<serde_json::Map<_, _>>();
let path = std::env::temp_dir().join(format!("dever-cost-sites-{}.json", std::process::id()));
std::fs::write(
&path,
serde_json::json!({"http":{"host":"127.0.0.1","port":18080},"sites":sites}).to_string(),
)
.unwrap();
let topics = names.iter().map(String::as_str).collect::<Vec<_>>();
let config = Config::load(&path, &topics).unwrap();
std::fs::remove_file(path).unwrap();
let mut request = Request::new(Default::default());
*request.uri_mut() = "/test".parse().unwrap();
request
.headers_mut()
.insert("host", "fixture.test".parse().unwrap());
measure("site_select_512", 10_000, || {
u64::from(config.accepts(black_box(&names[511]), black_box(&request)))
});
}
+277
View File
@@ -0,0 +1,277 @@
// 两侧共享 SQLite、连接池、截止时间与 worker,单独测量 Dever 生成层的额外成本。
// 不把此比较当成未经资源约束的同步 rusqlite、HTTP 吞吐或整个 core 的验收。
use dever_runtime::{
database::{Context, Id as ModelId, Parameter, Registry},
task::{Limits, Runtime, Scope},
time::calendar::DateTime,
value::uuid::Uuid,
};
use std::{hint::black_box, path::PathBuf, sync::Arc, time::Instant};
struct Directory(PathBuf);
impl Drop for Directory {
fn drop(&mut self) {
std::fs::remove_dir_all(&self.0).unwrap();
}
}
async fn rust_count(context: &mut Context, scope: &Scope<'_>) -> i64 {
context
.on(0, scope)
.await
.unwrap()
.query(
scope,
"SELECT COUNT(*) FROM \"demo_account_model\"",
vec![],
1,
|row| Ok(row.get::<_, i64>(0)?),
)
.await
.unwrap()
.pop()
.unwrap()
}
struct RustAccount {
id: ModelId<0>,
created_at: DateTime,
name: String,
note: Option<String>,
uid: Uuid,
rank: i64,
}
async fn rust_row(
context: &mut Context,
scope: &Scope<'_>,
update: bool,
) -> Result<RustAccount, dever_runtime::database::Error> {
let (sql, parameters) = if update {
(
"UPDATE \"demo_account_model\" SET \"name\" = ?2 WHERE id = ?1 RETURNING \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\"",
vec![
Parameter::Integer(1),
Parameter::Text("benchmark 中文".into()),
],
)
} else {
(
"SELECT \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\" FROM \"demo_account_model\" WHERE id = ?1",
vec![Parameter::Integer(1)],
)
};
let sql = dever_runtime::database::Sql {
sqlite: sql,
postgres: if update {
"UPDATE \"demo_account_model\" SET \"name\" = $2 WHERE id = $1 RETURNING \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\""
} else {
"SELECT \"id\",\"created_at\",\"name\",\"note\",\"uid\",\"rank\" FROM \"demo_account_model\" WHERE id = $1"
},
};
context
.on(0, scope)
.await?
.query(scope, sql, parameters, 1, |row| {
Ok(RustAccount {
id: ModelId(row.get::<_, i64>(0)?),
created_at: DateTime::from_millis(row.get::<_, i64>(1)?).map_err(|_| {
dever_runtime::database::Error::new(
dever_runtime::database::Kind::InvalidData,
"invalid stored DateTime",
)
})?,
name: row.get::<_, String>(2)?,
note: row.get::<_, Option<String>>(3)?,
uid: dever_runtime::database::scalar::uuid(row, 4)?,
rank: row.get::<_, i64>(5)?,
})
})
.await?
.pop()
.ok_or_else(|| {
dever_runtime::database::Error::new(
dever_runtime::database::Kind::NotFound,
"account was not found",
)
})
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Case {
Count,
Read,
Update,
}
fn measure(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
case: Case,
count: usize,
) -> (u128, i64) {
let mut context = Context::new(registry.clone());
runtime
.run(async |scope| {
let mut checksum = 0i64;
let start = Instant::now();
for _ in 0..count {
let value = if case != Case::Count {
let owner = (case == Case::Update).then(|| context.enter());
let row = if generated {
let value = if case == Case::Update {
GENERATED_UPDATE(
dever_runtime::database::Id(1),
"benchmark 中文",
scope,
&mut context,
)
.await
.unwrap()
} else {
GENERATED_READ(dever_runtime::database::Id(1), scope, &mut context)
.await
.unwrap()
};
RustAccount {
id: value.field0,
created_at: value.field1,
name: value.field2,
note: value.field3,
uid: value.field4,
rank: value.field5,
}
} else {
rust_row(&mut context, scope, case == Case::Update)
.await
.unwrap()
};
if let Some(owner) = owner {
context.finish(scope, owner, true).await.unwrap();
}
// 两侧都把完整记录交给调用者,不在 worker 内提前丢字段。
let row = black_box(&row);
assert_eq!(row.name, "benchmark 中文");
assert!(row.note.is_none());
row.id.0 + row.name.len() as i64
} else if generated {
GENERATED_COUNT(scope, &mut context).await.unwrap()
} else {
rust_count(&mut context, scope).await
};
checksum += black_box(value);
}
(start.elapsed().as_nanos(), checksum)
})
.unwrap()
}
fn main() {
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let relative = format!("database-benchmark-{}-{unique}", std::process::id());
let directory = Directory(
std::env::current_exe()
.unwrap()
.parent()
.unwrap()
.join(&relative),
);
std::fs::create_dir(&directory.0).unwrap();
let settings = directory.0.join("setting.json");
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 16,
})
.unwrap();
let schema = database_settings(&runtime, &directory.0, &settings, &relative);
let registry = runtime
.run(async |scope| database_registry(&settings, scope).await)
.unwrap()
.unwrap();
runtime
.run(async |scope| {
let mut context = Context::new(registry.clone());
let created = GENERATED_CREATE("benchmark 中文", scope, &mut context)
.await
.unwrap();
assert_eq!(created.field0.0, 1);
assert_eq!(
GENERATED_COUNT(scope, &mut context).await.unwrap(),
rust_count(&mut context, scope).await
);
let expected = (
created.field0,
created.field1,
created.field2,
created.field3,
created.field4,
created.field5,
);
let read = GENERATED_READ(ModelId(1), scope, &mut context)
.await
.unwrap();
assert_eq!(
(
read.field0,
read.field1,
read.field2,
read.field3,
read.field4,
read.field5
),
expected
);
let read = rust_row(&mut context, scope, false).await.unwrap();
assert_eq!(
(
read.id,
read.created_at,
read.name,
read.note,
read.uid,
read.rank
),
expected
);
})
.unwrap();
println!("case,sample,implementation,nanoseconds,checksum");
for (name, case) in [
("database_count", Case::Count),
("database_get", Case::Read),
("database_update_commit", Case::Update),
] {
if let Ok(selected) = std::env::var("DEVER_BENCH_CASES")
&& !selected.split(',').any(|item| item == name)
{
continue;
}
for generated in [true, false] {
black_box(measure(&runtime, &registry, generated, case, 200));
}
for sample in 0..15 {
let mut order = [("dever", true), ("rust", false), ("control", true)];
order.rotate_left(sample % 3);
let mut expected = None;
for (implementation, generated) in order {
let (elapsed, checksum) = measure(&runtime, &registry, generated, case, 2000);
if let Some(expected) = expected {
assert_eq!(checksum, expected);
}
expected = Some(checksum);
println!("{name},{sample},{implementation},{elapsed},{checksum}");
}
}
}
drop(registry);
if let Some(schema) = schema {
schema.close(&runtime);
}
drop(runtime);
drop(directory);
}
+74
View File
@@ -0,0 +1,74 @@
// PostgreSQL 只读取显式测试连接;每个比较进程创建并删除独占 schema。
struct PostgresSchema {
database: dever_runtime::database::Database,
drop_sql: &'static str,
}
impl PostgresSchema {
fn close(self, runtime: &Runtime) {
runtime
.run(async |scope| {
self.database
.executor()
.await
.unwrap()
.initialize(scope, self.drop_sql)
.await
.unwrap();
})
.unwrap();
self.database.close();
}
}
fn database_settings(
runtime: &Runtime,
directory: &std::path::Path,
settings: &std::path::Path,
relative: &str,
) -> Option<PostgresSchema> {
if !POSTGRES {
std::fs::write(settings, format!(r#"{{"database":{{"default":{{"type":"sqlite","path":"{relative}/case.db","max_connections":1,"max_page_size":20}}}}}}"#)).unwrap();
return None;
}
let test_settings = std::env::var_os("DEVER_BENCH_POSTGRES_SETTINGS")
.expect("PostgreSQL benchmarks require an explicit isolated test configuration");
let config: serde_json::Value =
serde_json::from_slice(&std::fs::read(test_settings).unwrap()).unwrap();
let mut connection = config["database"]["postgres_test"].clone();
let options: dever_runtime::database::Options =
serde_json::from_value(connection.clone()).unwrap();
assert_eq!(options.driver, "postgres");
let name = format!("dever_bench_{}", std::process::id());
let create_sql: &'static str = Box::leak(format!("CREATE SCHEMA {name}").into_boxed_str());
let drop_sql = Box::leak(format!("DROP SCHEMA {name} CASCADE").into_boxed_str());
let database = runtime
.run(async |scope| {
let database = dever_runtime::database::Database::open(directory, options, scope)
.await
.unwrap();
database
.executor()
.await
.unwrap()
.initialize(scope, create_sql)
.await
.unwrap();
database
})
.unwrap();
let url = connection["url"].as_str().unwrap();
assert!(
!url.contains('?'),
"test connection must not override schema options"
);
connection["url"] = format!("{url}?options=-csearch_path%3D{name}").into();
connection["max_connections"] = 1.into();
connection["max_page_size"] = 20.into();
std::fs::write(
settings,
serde_json::to_vec(&serde_json::json!({"database":{"default":connection}})).unwrap(),
)
.unwrap();
Some(PostgresSchema { database, drop_sql })
}
@@ -0,0 +1,266 @@
use dever_runtime::async_stream::AsyncStream;
use dever_runtime::channel::Channel;
use dever_runtime::task::{Group, Limits, Runtime, Scope, Task};
struct ConcurrencyCase {
runtime: Runtime,
values: Vec<i64>,
limit: i64,
}
fn concurrency_fault() -> Fault {
&FaultInfo {
message: "extended concurrency failed",
source: "extended-concurrency-reference",
line: 1,
column: 1,
}
}
type ChannelOutput = (Vec<Option<i64>>, bool);
fn channel_generated(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
let Some(value) = values.first() else {
return Ok((Vec::new(), true));
};
value
.runtime
.run(async |scope| EXT_CHANNEL(&value.values, value.limit, scope).await)
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
async fn send_values(values: Vec<i64>, channel: Channel<i64>) -> Result<(), Fault> {
for value in values {
channel.send(value).await.map_err(|_| concurrency_fault())?;
}
channel.close().await.map_err(|_| concurrency_fault())
}
fn channel_reference(values: &[ConcurrencyCase]) -> Result<ChannelOutput, Fault> {
let Some(value) = values.first() else {
return Ok((Vec::new(), true));
};
value
.runtime
.run(async |scope| {
let channel = Channel::new(value.limit).map_err(|_| concurrency_fault())?;
let producer = channel.clone();
let owned = value.values.clone();
let pending = scope
.spawn_leaf(move || send_values(owned, producer))
.map_err(|_| concurrency_fault())?;
let mut received = Vec::with_capacity(value.values.len());
for _ in &value.values {
received.push(channel.receive().await.map_err(|_| concurrency_fault())?);
}
pending.wait().await.map_err(|_| concurrency_fault())??;
let ended = channel
.receive()
.await
.map_err(|_| concurrency_fault())?
.is_none();
Ok((received, ended))
})
.map_err(|_| concurrency_fault())?
}
fn channel_score(value: &ChannelOutput) -> u64 {
value.0.iter().fold(u64::from(value.1), |score, item| {
score
.wrapping_mul(31)
.wrapping_add(item.unwrap_or(0) as u64)
})
}
async fn stream_total(stream: &AsyncStream<i64>, limit: Option<i64>) -> Result<i64, Fault> {
let _close = stream.close_on_drop();
let mut total = 0i64;
while let Some(value) = stream.next().await.map_err(|_| concurrency_fault())? {
total = total.checked_add(value).ok_or_else(concurrency_fault)?;
if limit.is_some_and(|limit| value >= limit) {
break;
}
}
Ok(total)
}
fn pull_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(EXT_PULL(&value.values))
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn prefix_generated(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(EXT_PREFIX(&value.values))
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn stream_reference<const EARLY: bool>(values: &[ConcurrencyCase]) -> Result<(i64, i64), Fault> {
let Some(value) = values.first() else {
return Ok((0, 0));
};
value
.runtime
.run_leaf(async {
let stream = AsyncStream::values(value.values.clone());
let alias = stream.clone();
let total = stream_total(&stream, EARLY.then_some(8)).await?;
let after = stream_total(&alias, None).await?;
Ok((total, after))
})
.map_err(|_| concurrency_fault())?
}
fn stream_score(value: &(i64, i64)) -> u64 {
(value.0 as u64)
.wrapping_mul(31)
.wrapping_add(value.1 as u64)
}
async fn parallel_reference_inner(value: &ConcurrencyCase, scope: &Scope<'_>) -> Result<i64, Fault> {
let channel = Channel::new(1).map_err(|_| concurrency_fault())?;
let consumer = channel.clone();
let pending = scope
.spawn_leaf(
move || async move { stream_total(&AsyncStream::channel(consumer), None).await },
)
.map_err(|_| concurrency_fault())?;
let mut items = value.values.iter().copied();
let group: Group<Task<Result<(), Fault>>, Fault> =
Group::new(value.limit).map_err(|_| concurrency_fault())?;
group
.each(
|| std::future::ready(Ok(items.next())),
|item| {
let target = channel.clone();
scope.spawn_leaf(move || async move {
target.send(item).await.map_err(|_| concurrency_fault())
})
},
)
.await
.map_err(|_| concurrency_fault())??;
channel.close().await.map_err(|_| concurrency_fault())?;
pending.wait().await.map_err(|_| concurrency_fault())?
}
fn parallel_generated(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.runtime
.run(async |scope| EXT_PARALLEL(&value.values, value.limit, scope).await)
.map_err(|_| concurrency_fault())?
.map_err(|_| concurrency_fault())
}
fn parallel_reference(values: &[ConcurrencyCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.runtime
.run(async |scope| parallel_reference_inner(value, scope).await)
.map_err(|_| concurrency_fault())?
}
fn new_case(limit: i64) -> ConcurrencyCase {
ConcurrencyCase {
runtime: Runtime::new(Limits {
threads: 2,
workers: 2,
tasks: 8,
})
.unwrap(),
values: (1..=256).collect(),
limit,
}
}
fn main() {
// 两侧共用运行库与固定限额;计时含复制、提交、领取及作用域清理。
let mut case = new_case(1);
assert_eq!(
case.runtime.run_leaf(EXT_BOUNDARY()).unwrap().unwrap(),
(15, Some(9), 0)
);
let expected = case.values.iter().sum::<i64>();
println!("case,sample,implementation,elapsed_ns,checksum");
for capacity in [1, 64] {
case.limit = capacity;
let values = std::slice::from_ref(&case);
let received = case.values.iter().copied().map(Some).collect::<Vec<_>>();
assert_eq!(channel_generated(values).unwrap(), (received.clone(), true));
assert_eq!(channel_reference(values).unwrap(), (received, true));
compare_named(
&format!("channel_capacity_{capacity}_shared_runtime"),
values,
32,
channel_generated,
channel_reference,
channel_score,
"rust_runtime",
);
}
case.limit = 8;
let values = std::slice::from_ref(&case);
assert_eq!(pull_generated(values).unwrap(), (expected, 0));
assert_eq!(prefix_generated(values).unwrap(), (36, 0));
compare_named(
"async_stream_pull_shared_runtime",
values,
128,
pull_generated,
stream_reference::<false>,
stream_score,
"rust_runtime",
);
compare_named(
"async_stream_prefix_shared_runtime",
values,
128,
prefix_generated,
stream_reference::<true>,
stream_score,
"rust_runtime",
);
for limit in [1, 4] {
case.limit = limit;
let values = std::slice::from_ref(&case);
assert_eq!(parallel_generated(values).unwrap(), expected);
assert_eq!(parallel_reference(values).unwrap(), expected);
compare_named(
&format!("parallel_each_limit_{limit}_shared_runtime"),
values,
32,
parallel_generated,
parallel_reference,
|value| *value as u64,
"rust_runtime",
);
}
// 真正调用空输入 fixture,区别于 samples 的空用例切片。
case.values.clear();
let empty = std::slice::from_ref(&case);
assert_eq!(channel_generated(empty).unwrap(), (Vec::new(), true));
assert_eq!(channel_reference(empty).unwrap(), (Vec::new(), true));
assert_eq!(pull_generated(empty).unwrap(), (0, 0));
assert_eq!(prefix_generated(empty).unwrap(), (0, 0));
assert_eq!(stream_reference::<false>(empty).unwrap(), (0, 0));
assert_eq!(stream_reference::<true>(empty).unwrap(), (0, 0));
assert_eq!(parallel_generated(empty).unwrap(), 0);
assert_eq!(parallel_reference(empty).unwrap(), 0);
}
+45
View File
@@ -0,0 +1,45 @@
use std::path::Path;
pub fn run(core: &Path) -> bool {
let all = std::env::args().any(|argument| argument == "--extended");
let primitives = all || std::env::args().any(|argument| argument == "--extended-primitives");
let concurrency = all || std::env::args().any(|argument| argument == "--extended-concurrency");
if primitives {
crate::compare(
core,
"extended-performance/primitives",
"extended-primitives",
&[include_str!("../samples.rs"), include_str!("primitives.rs")].concat(),
&[
("EXT_SHA256", "sha256Digest"),
("EXT_HMAC", "hmacDigest"),
("EXT_DATETIME", "datetimeBatch"),
("EXT_DATE", "dateBatch"),
("EXT_TIME", "timeBatch"),
("EXT_UUID", "uuidBatch"),
],
&[],
);
}
if concurrency {
crate::compare(
core,
"extended-performance/concurrency",
"extended-concurrency",
&[
include_str!("../samples.rs"),
include_str!("concurrency.rs"),
]
.concat(),
&[
("EXT_CHANNEL", "channelRoundtrip"),
("EXT_PULL", "streamPull"),
("EXT_PREFIX", "streamPrefix"),
("EXT_BOUNDARY", "streamBoundary"),
("EXT_PARALLEL", "parallelBounded"),
],
&[],
);
}
primitives || concurrency
}
@@ -0,0 +1,274 @@
use dever_runtime::time::calendar::{Date, DateTime, Time};
use dever_runtime::value::secret::Secret;
use sha2::{Digest, Sha256};
fn primitive_fault() -> Fault {
&FaultInfo {
message: "extended primitive failed",
source: "extended-primitives-reference",
line: 1,
column: 1,
}
}
fn digest_score(bytes: &dever_runtime::Bytes) -> u64 {
bytes.iter().fold(0u64, |score, byte| {
score.wrapping_mul(31).wrapping_add(u64::from(*byte))
})
}
fn sha_reference(bytes: &dever_runtime::Bytes) -> Result<dever_runtime::Bytes, Fault> {
Ok(Sha256::digest(bytes).to_vec().into())
}
struct HmacCase {
key: Secret,
bytes: dever_runtime::Bytes,
}
fn hmac_generated(values: &[HmacCase]) -> Result<dever_runtime::Bytes, Fault> {
let Some(value) = values.first() else {
return Ok(dever_runtime::Bytes::new());
};
EXT_HMAC(&value.key, &value.bytes)
}
// 共用批准的 Secret 接收点;不读取密钥内部字节。
fn hmac_reference(values: &[HmacCase]) -> Result<dever_runtime::Bytes, Fault> {
Ok(values
.first()
.map_or_else(Vec::new, |value| {
dever_runtime::crypto::hmac_sha256(&value.key, &value.bytes)
})
.into())
}
fn datetime_generated(values: &[String]) -> Result<Vec<String>, Fault> {
EXT_DATETIME(values).map_err(|_| primitive_fault())
}
fn datetime_reference(values: &[String]) -> Result<Vec<String>, Fault> {
values
.iter()
.map(|value| {
DateTime::parse(value)
.and_then(DateTime::format)
.map_err(|_| primitive_fault())
})
.collect()
}
fn date_generated(values: &[String]) -> Result<Vec<String>, Fault> {
EXT_DATE(values).map_err(|_| primitive_fault())
}
fn date_reference(values: &[String]) -> Result<Vec<String>, Fault> {
values
.iter()
.map(|value| {
Date::parse(value)
.and_then(Date::format)
.map_err(|_| primitive_fault())
})
.collect()
}
fn time_generated(values: &[String]) -> Result<Vec<String>, Fault> {
EXT_TIME(values).map_err(|_| primitive_fault())
}
fn time_reference(values: &[String]) -> Result<Vec<String>, Fault> {
values
.iter()
.map(|value| {
Time::parse(value)
.and_then(Time::format)
.map_err(|_| primitive_fault())
})
.collect()
}
fn text_score(values: &Vec<String>) -> u64 {
values
.iter()
.flat_map(|value| value.bytes())
.fold(0u64, |score, byte| {
score.wrapping_mul(31).wrapping_add(u64::from(byte))
})
}
fn uuid_generated(values: &[String]) -> Result<Vec<Option<String>>, Fault> {
EXT_UUID(values)
}
fn uuid_reference(values: &[String]) -> Result<Vec<Option<String>>, Fault> {
Ok(values
.iter()
.map(|value| {
if value.len() != 36 {
return None;
}
uuid::Uuid::try_parse(value)
.ok()
.map(|value| value.hyphenated().to_string())
})
.collect())
}
fn uuid_score(values: &Vec<Option<String>>) -> u64 {
values.iter().fold(0u64, |score, value| {
value.as_ref().map_or(score.wrapping_add(1), |value| {
value.bytes().fold(score.wrapping_add(2), |score, byte| {
score.wrapping_mul(31).wrapping_add(u64::from(byte))
})
})
})
}
fn check_primitives() {
assert_eq!(
EXT_SHA256(&dever_runtime::Bytes::from_static(b"abc"))
.unwrap()
.as_ref(),
[
0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae,
0x22, 0x23, 0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61,
0xf2, 0x00, 0x15, 0xad,
]
);
// 沿用已有密码学验收的公开向量,不使用真实凭据。
let vector = [HmacCase {
key: Secret::from_input(vec![0x0b; 20]),
bytes: dever_runtime::Bytes::from_static(b"Hi There"),
}];
assert_eq!(
hmac_generated(&vector).unwrap().as_ref(),
[
0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b,
0xf1, 0x2b, 0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c,
0x2e, 0x32, 0xcf, 0xf7,
]
);
assert_eq!(
hmac_generated(&vector).unwrap(),
hmac_reference(&vector).unwrap()
);
assert_eq!(
datetime_generated(&["2024-02-29T12:34:56.12+08:00".into()]).unwrap(),
["2024-02-29T04:34:56.120Z"]
);
assert_eq!(
time_generated(&["12:34:56.000".into()]).unwrap(),
["12:34:56"]
);
for value in ["2023-02-29", "2024-2-29"] {
let values = [value.to_owned()];
assert!(date_generated(&values).is_err());
assert!(date_reference(&values).is_err());
}
for value in ["24:00:00", "12:34:56.1"] {
let values = [value.to_owned()];
assert!(time_generated(&values).is_err());
assert!(time_reference(&values).is_err());
}
let values = ["2024-02-29T00:00:00.1234Z".to_owned()];
assert!(datetime_generated(&values).is_err());
assert!(datetime_reference(&values).is_err());
}
fn main() {
check_primitives();
println!("case,sample,implementation,elapsed_ns,checksum");
for size in [0usize, 64, 4096] {
let bytes = dever_runtime::Bytes::from(vec![0x61; size]);
compare_bytes(
&format!("crypto_sha256_{size}"),
&bytes,
2000,
EXT_SHA256,
sha_reference,
digest_score,
);
let values = [HmacCase {
key: Secret::from_input(vec![0x0b; 20]),
bytes,
}];
compare_named(
&format!("crypto_hmac_{size}_shared_runtime"),
&values,
2000,
hmac_generated,
hmac_reference,
digest_score,
"rust_runtime",
);
}
// 日历参照保留相同精度、年份范围、非法输入和 UTC 规范化规则。
let datetimes = [
"1970-01-01T00:00:00Z",
"2024-02-29T12:34:56.12+08:00",
"1969-12-31T23:59:59.999Z",
]
.map(str::to_owned);
let dates = ["2024-02-29", "2000-01-01", "9999-12-31"].map(str::to_owned);
let times = ["00:00:00", "12:34:56.000", "23:59:59.999"].map(str::to_owned);
compare_named(
"calendar_datetime_shared_runtime",
&datetimes,
2000,
datetime_generated,
datetime_reference,
text_score,
"rust_runtime",
);
compare_named(
"calendar_date_shared_runtime",
&dates,
2000,
date_generated,
date_reference,
text_score,
"rust_runtime",
);
compare_named(
"calendar_time_shared_runtime",
&times,
2000,
time_generated,
time_reference,
text_score,
"rust_runtime",
);
let valid = [
"550E8400-E29B-41D4-A716-446655440000",
"00000000-0000-0000-0000-000000000000",
]
.map(str::to_owned);
assert_eq!(
uuid_generated(&valid).unwrap()[0].as_deref(),
Some("550e8400-e29b-41d4-a716-446655440000")
);
let invalid = [
"",
"550e8400e29b41d4a716446655440000",
"00000000-0000-0000-0000-00000000000-",
]
.map(str::to_owned);
assert_eq!(uuid_generated(&invalid).unwrap(), vec![None; invalid.len()]);
compare(
"uuid_parse_format",
&valid,
2000,
uuid_generated,
uuid_reference,
uuid_score,
);
compare(
"uuid_parse_invalid",
&invalid,
2000,
uuid_generated,
uuid_reference,
uuid_score,
);
}
+244
View File
@@ -0,0 +1,244 @@
fn fault() -> Fault {
&FaultInfo {
message: "integer arithmetic fault",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn integer_reference(values: &[i64]) -> Result<i64, Fault> {
values
.first()
.copied()
.unwrap_or(0)
.checked_add(1)
.ok_or_else(fault)
}
fn integer_generated(values: &[i64]) -> Result<i64, Fault> {
GENERATED_INTEGER(values.first().copied().unwrap_or(0))
}
fn map_reference(values: &[i64]) -> Result<Vec<i64>, Fault> {
let mut output = Vec::with_capacity(values.len());
for value in values {
output.push(value.checked_add(1).ok_or_else(fault)?);
}
Ok(output)
}
fn fold_reference(values: &[i64]) -> Result<i64, Fault> {
let mut state = 0i64;
for value in values {
state = state.checked_add(*value).ok_or_else(fault)?;
}
Ok(state)
}
fn text_reference(values: &[String]) -> Result<String, Fault> {
let value = values.first().map(String::as_str).unwrap_or("");
let mut result = String::with_capacity(value.len() + "新".len());
result.push_str(value);
result.push_str("新");
Ok(result)
}
fn text_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_TEXT(values.first().map(String::as_str).unwrap_or(""))
}
fn text_direct_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_APPEND(values.first().map(String::as_str).unwrap_or(""))
}
fn append_reference(value: &str) -> Result<String, Fault> {
let mut result = String::with_capacity(value.len() + "新".len());
result.push_str(value);
result.push_str("新");
Ok(result)
}
fn text_helper_reference(values: &[String]) -> Result<String, Fault> {
append_reference(values.first().map(String::as_str).unwrap_or(""))
}
fn rows_reference(values: &[ROW_TYPE]) -> Result<Vec<ROW_TYPE>, Fault> {
let mut result = Vec::with_capacity(values.len());
for value in values {
let mut name = String::with_capacity(value.field0.len() + "新".len());
name.push_str(&value.field0);
name.push_str("新");
result.push(ROW_TYPE {
field0: name,
field1: value.field1.checked_add(1).ok_or_else(fault)?,
});
}
Ok(result)
}
fn capture_reference(values: &[i64]) -> Result<Vec<RESULT_TYPE>, Fault> {
let mut result = Vec::with_capacity(values.len());
for value in values {
result.push(if *value < 0 {
RESULT_TYPE::Variant1(PROBLEM_TYPE::Variant0(*value))
} else {
RESULT_TYPE::Variant0(*value)
});
}
Ok(result)
}
fn capture_generated(values: &[i64]) -> Result<Vec<RESULT_TYPE>, Fault> {
let mut result = Vec::with_capacity(values.len());
for value in values {
result.push(GENERATED_CAPTURE(*value)?);
}
Ok(result)
}
// 观察部分构造后失败的清理,成功返回的 bool 保持共享计时/内存入口不变。
fn map_failed_generated(values: &[i64]) -> Result<bool, Fault> {
Ok(GENERATED_MAP(values).is_err())
}
fn map_failed_reference(values: &[i64]) -> Result<bool, Fault> {
Ok(map_reference(values).is_err())
}
fn rows_failed_generated(values: &[ROW_TYPE]) -> Result<bool, Fault> {
Ok(GENERATED_ROWS(values).is_err())
}
fn rows_failed_reference(values: &[ROW_TYPE]) -> Result<bool, Fault> {
Ok(rows_reference(values).is_err())
}
fn main() {
let values: Vec<i64> = (0..1024).map(|value| (value * 17) % 1000 - 1).collect();
let failures: Vec<i64> = (0..128).map(|value| (value % 8) - 1).collect();
let rows: Vec<_> = (0..64)
.map(|index| ROW_TYPE {
field0: "雪山树林".repeat(4),
field1: index,
})
.collect();
let texts = ["雪山树林".repeat(16)];
eprintln!(
"text_forward_equals_direct_address={}",
std::ptr::fn_addr_eq(
text_generated as fn(&[String]) -> Result<String, Fault>,
text_direct_generated as fn(&[String]) -> Result<String, Fault>
)
);
for values in [&[i64::MAX][..], &[i64::MAX, 1][..], &[i64::MIN, -1][..]] {
assert_eq!(
integer_generated(values).map_err(|_| ()),
integer_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_MAP(values).map_err(|_| ()),
map_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_FOLD(values).map_err(|_| ()),
fold_reference(values).map_err(|_| ())
);
assert_eq!(
capture_generated(values).unwrap(),
capture_reference(values).unwrap()
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"handler_integer",
&[41],
3000000,
integer_generated,
integer_reference,
|value| *value as u64,
);
compare(
"handler_map",
&values,
20000,
GENERATED_MAP,
map_reference,
|value| value.len() as u64,
);
compare(
"handler_fold",
&values,
30000,
GENERATED_FOLD,
fold_reference,
|value| *value as u64,
);
compare(
"handler_text",
&texts,
300000,
text_generated,
text_reference,
|value| value.len() as u64,
);
compare_borrowed(
"handler_text",
&texts,
300000,
text_generated,
text_reference,
|value| value.len() as u64,
);
compare(
"handler_text_helper",
&texts,
300000,
text_generated,
text_helper_reference,
|value| value.len() as u64,
);
compare_named(
"handler_text_direct",
&texts,
300000,
text_generated,
text_direct_generated,
|value| value.len() as u64,
"direct",
);
compare(
"handler_record",
&rows,
5000,
GENERATED_ROWS,
rows_reference,
|value| value.len() as u64,
);
compare(
"handler_capture",
&failures,
30000,
capture_generated,
capture_reference,
|value| value.len() as u64,
);
compare(
"handler_map_failure",
&[1, 2, i64::MAX],
100000,
map_failed_generated,
map_failed_reference,
|value| u64::from(*value),
);
let mut failed_rows = rows;
failed_rows.last_mut().unwrap().field1 = i64::MAX;
compare(
"handler_record_failure",
&failed_rows,
5000,
rows_failed_generated,
rows_failed_reference,
|value| u64::from(*value),
);
}
+202
View File
@@ -0,0 +1,202 @@
use indexmap::IndexMap;
struct Lookup {
generated: Map<Id, i64>,
reference: IndexMap<Id, i64>,
keys: Vec<Id>,
}
struct Comparison {
values: Vec<Id>,
expected: Id,
}
fn id_fault() -> Fault {
&FaultInfo {
message: "integer arithmetic fault",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn roundtrip_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_ROUNDTRIP(values.first().map(String::as_str).unwrap_or(""))
}
fn roundtrip_reference(values: &[String]) -> Result<String, Fault> {
let id = Id::from(values.first().map(String::as_str).unwrap_or("").to_owned());
Ok(id.into_text())
}
fn encode_reference(values: &[String]) -> Result<Vec<Id>, Fault> {
Ok(values.iter().map(|text| Id::from(text.clone())).collect())
}
fn compare_generated(values: &[Comparison]) -> Result<i64, Fault> {
match values.first() {
Some(value) => GENERATED_COMPARE(&value.values, &value.expected),
None => Ok(0),
}
}
fn compare_reference(values: &[Comparison]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let expected = value.expected.clone();
let mut count = 0i64;
for item in &value.values {
count = count
.checked_add(i64::from(item == &expected))
.ok_or_else(id_fault)?;
}
Ok(count)
}
fn lookup_generated(values: &[Lookup]) -> Result<i64, Fault> {
match values.first() {
Some(value) => GENERATED_LOOKUP(&value.generated, &value.keys),
None => Ok(0),
}
}
fn lookup_reference(values: &[Lookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut count = 0i64;
for key in &value.keys {
count = count
.checked_add(value.reference.get(key).copied().unwrap_or(0))
.ok_or_else(id_fault)?;
}
Ok(count)
}
fn lookup_same_table(values: &[Lookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut count = 0i64;
for key in &value.keys {
count = count
.checked_add(value.generated.get(key).copied().unwrap_or(0))
.ok_or_else(id_fault)?;
}
Ok(count)
}
fn map_reference(values: &[String]) -> Result<Vec<(Id, i64)>, Fault> {
let mut map = IndexMap::new();
for text in values {
map.insert(Id::from(text.clone()), text.chars().count() as i64);
}
Ok(map.into_iter().collect())
}
fn main() {
let texts: Vec<String> = (0..512)
.map(|index| format!("user-{index:04}-雪"))
.collect();
let short = ["user-0012-雪".to_owned()];
let long = ["雪山树林".repeat(512)];
let reference = || {
texts
.iter()
.map(|text| (Id::from(text.clone()), text.chars().count() as i64))
.collect()
};
// 建表顺序影响独立表的哈希与分配布局;复测须等量覆盖两种顺序。
let (generated, reference) = match std::env::var("DEVER_BENCH_MAP_ORDER")
.as_deref()
.unwrap_or("dever-first")
{
"dever-first" => {
let generated = GENERATED_BUILD_MAP(&texts).unwrap();
(generated, reference())
}
"rust-first" => {
let reference = reference();
(GENERATED_BUILD_MAP(&texts).unwrap(), reference)
}
order => panic!("unknown table construction order: {order}"),
};
let keys = (0..1024)
.map(|index| Id::from(format!("user-{:04}-雪", (index * 17) % 576)))
.collect();
let lookups = [Lookup {
generated,
reference,
keys,
}];
let comparisons = [Comparison {
values: (0..1024)
.map(|index| Id::from(format!("user-{:04}-雪", index % 32)))
.collect(),
expected: Id::from("user-0012-雪".to_owned()),
}];
assert_eq!(
GENERATED_ENTRIES(&["a".into(), "b".into(), "a".into()]).unwrap(),
map_reference(&["a".into(), "b".into(), "a".into()]).unwrap()
);
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"id_roundtrip_short",
&short,
300000,
roundtrip_generated,
roundtrip_reference,
|value| value.len() as u64,
);
compare(
"id_roundtrip_long",
&long,
20000,
roundtrip_generated,
roundtrip_reference,
|value| value.len() as u64,
);
compare(
"id_each",
&texts,
300,
GENERATED_ENCODE_ALL,
encode_reference,
|value| value.len() as u64,
);
compare(
"id_compare",
&comparisons,
2000,
compare_generated,
compare_reference,
|value| *value as u64,
);
compare(
"id_map_lookup",
&lookups,
1000,
lookup_generated,
lookup_reference,
|value| *value as u64,
);
// 双方读取同一张表,避免将各自随机哈希状态的差异归给生成代码。
compare_named(
"id_map_lookup_same_table",
&lookups,
1000,
lookup_generated,
lookup_same_table,
|value| *value as u64,
"rust_runtime",
);
compare(
"id_map_build",
&texts,
300,
GENERATED_ENTRIES,
map_reference,
|value| value.len() as u64,
);
}
+37
View File
@@ -0,0 +1,37 @@
//! 固定 payload 指纹来自本基准的公开记录合同;不把生成函数作为 Rust 参考实现。
use dever::compiler::model::Program;
use sha2::{Digest, Sha256};
use std::path::Path;
pub fn run(core: &Path) {
crate::compare(
core,
"job-performance",
"jobs",
&[
include_str!("../auth/shared.rs"),
include_str!("reference.rs"),
include_str!("measure.rs"),
include_str!("checks.rs"),
]
.concat(),
&[("GENERATED_SUBMIT", "bench.queue.app.submit")],
&[],
);
}
pub fn metadata(program: &Program, reference: &str) -> String {
assert_eq!(program.jobs.len(), 1);
assert_eq!(program.models.len(), 1);
assert!(!program.has_roles());
assert_eq!(program.jobs[0].name, "bench.queue.job.publish");
let schema = format!(
"{:x}",
Sha256::digest(b"bench.queue.Payload{name:Text,amount:Int}")
);
reference
.replace("JOB_BINDING", &program.models.len().to_string())
.replace("RESULT_MODEL", &program.models[0].definition.to_string())
.replace("JOB_SCHEMA", &format!("{schema:?}"))
}
+131
View File
@@ -0,0 +1,131 @@
// 独立 SQL 校验持久队列和业务结果,不能只用函数返回值或随机 ID 长度认定正确。
async fn clear(scope: &Scope<'_>, registry: &Arc<Registry>) {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
executor
.execute(scope, "DELETE FROM _dever_job", vec![])
.await
.unwrap();
executor
.execute(scope, "DELETE FROM bench_queue_model", vec![])
.await
.unwrap();
}
async fn check_rows(scope: &Scope<'_>, registry: &Arc<Registry>, inputs: &[JobInput], handled: bool) {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
let rows = executor.query(scope,
"SELECT id,target,payload,schema,execution,task_key,state,attempt,retries,timeout_ms,token,lease_until,available_at,created_at FROM _dever_job ORDER BY task_key",
vec![], inputs.len() + 1, |row| Ok((
row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?,
row.get::<_, String>(3)?, row.get::<_, String>(4)?, row.get::<_, String>(5)?,
row.get::<_, String>(6)?, row.get::<_, i64>(7)?, row.get::<_, i64>(8)?, row.get::<_, i64>(9)?,
row.get::<_, Option<String>>(10)?, row.get::<_, Option<i64>>(11)?, row.get::<_, i64>(12)?, row.get::<_, i64>(13)?,
))).await.unwrap();
assert_eq!(rows.len(), inputs.len());
let mut unique = std::collections::BTreeSet::new();
for (row, input) in rows.iter().zip(inputs) {
assert!(unique.insert(&row.0));
assert_eq!(row.0.len(), 32);
assert_eq!(row.1, TARGET);
let payload: RustPayload = dever_runtime::wire::decode(&row.2).unwrap();
assert_eq!(payload.name, input.name);
assert_eq!(payload.amount, input.amount);
assert_eq!(row.3, SCHEMA);
assert!(matches!(
dever_runtime::wire::decode::<Execution>(&row.4).unwrap(),
Execution::System { tenant: None }
));
assert_eq!(row.5, input.key);
assert_eq!(row.6, if handled { "completed" } else { "pending" });
assert_eq!((row.7, row.8, row.9), (i64::from(handled), 2, 1000));
assert!(row.10.is_none() && row.11.is_none());
assert!(row.12 >= row.13 && row.13 > 0);
}
let results = executor
.query(
scope,
"SELECT name,amount FROM bench_queue_model ORDER BY name",
vec![],
inputs.len() + 1,
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i64>(1)?)),
)
.await
.unwrap();
let expected = if handled {
inputs
.iter()
.map(|input| (input.name.clone(), input.amount * 3 + 7))
.collect::<Vec<_>>()
} else {
vec![]
};
assert_eq!(results, expected);
}
fn check_jobs(runtime: &Runtime, registry: &Arc<Registry>) {
runtime
.run(async |scope| {
for generated in [true, false] {
clear(scope, registry).await;
let inputs = inputs(2);
for input in &inputs {
let id = submit(generated, input, scope, registry).await;
assert_eq!(submit(generated, input, scope, registry).await, id);
}
check_rows(scope, registry, &inputs, false).await;
for claim in claims(scope, registry, 2).await {
execute(generated, claim, scope, registry).await.unwrap();
}
check_rows(scope, registry, &inputs, true).await;
clear(scope, registry).await;
let input = JobInput {
name: "rollback".into(),
amount: -4,
key: "rollback".into(),
};
submit(generated, &input, scope, registry).await;
let claim = claims(scope, registry, 1).await.pop().unwrap();
assert!(matches!(
execute(generated, claim, scope, registry).await,
Err(JobFailure::Handler)
));
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
assert_eq!(
executor
.query(
scope,
"SELECT COUNT(*) FROM bench_queue_model",
vec![],
1,
|row| Ok(row.get::<_, i64>(0)?)
)
.await
.unwrap(),
[0]
);
assert_eq!(
executor
.query(scope, "SELECT state FROM _dever_job", vec![], 1, |row| Ok(
row.get::<_, String>(0)?
))
.await
.unwrap(),
["running"]
);
drop(database);
clear(scope, registry).await;
submit(generated, &inputs[0], scope, registry).await;
let claim = claims(scope, registry, 1).await.pop().unwrap();
let mut claim = Arc::try_unwrap(claim).unwrap();
claim.payload = r#"["array-is-not-a-record",1]"#.into();
assert!(matches!(
execute(generated, Arc::new(claim), scope, registry).await,
Err(JobFailure::Blocked)
));
}
})
.unwrap();
}
+267
View File
@@ -0,0 +1,267 @@
// 入队计时含具体编码和外层提交;执行计时含真实生成分派、任务作用域、解码、业务写入和确认。
// 单阶段对照把 claim 放在计时外;完整循环另计入入队、claim、执行和确认。
struct JobInput {
name: String,
amount: i64,
key: String,
}
fn inputs(count: usize) -> Vec<JobInput> {
(0..count)
.map(|index| JobInput {
name: format!("job-{index:04} 中文\""),
amount: index as i64 % 997,
key: format!("key-{index:04}"),
})
.collect()
}
async fn submit(
generated: bool,
input: &JobInput,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> String {
let mut database = DbContext::new(registry.clone());
let owner = database.enter();
let execution = Execution::System { tenant: None };
let id = if generated {
GENERATED_SUBMIT(
&input.name,
input.amount,
&input.key,
scope,
&mut database,
&execution,
)
.await
.unwrap()
} else {
rust_submit(
&input.name,
input.amount,
&input.key,
scope,
&mut database,
&execution,
)
.await
.unwrap()
};
database.finish(scope, owner, true).await.unwrap();
id.into_text()
}
async fn execute(
generated: bool,
claim: Arc<Claim>,
scope: &Scope<'_>,
registry: &Arc<Registry>,
) -> Result<(), JobFailure> {
let registry = registry.clone();
let timeout = claim.policy.timeout_ms;
let outcome = if generated {
scope
.spawn(move |child| job_execute(JOB_BINDING, None, claim, child, registry))
.unwrap()
.timeout(timeout)
.await
.unwrap()
} else {
scope
.spawn(move |child| rust_execute(JOB_BINDING, claim, child, registry))
.unwrap()
.timeout(timeout)
.await
.unwrap()
};
match outcome {
dever_runtime::task::Timed::Ready(value) => value,
_ => panic!("benchmark handler timed out"),
}
}
async fn claims(scope: &Scope<'_>, registry: &Arc<Registry>, count: usize) -> Vec<Arc<Claim>> {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
let mut claims = Vec::with_capacity(count);
for _ in 0..count {
let claim = job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000,
)
.await
.unwrap()
.unwrap();
assert_eq!(claim.schema, SCHEMA);
assert_eq!(claim.target, TARGET);
assert_eq!(claim.attempt, 1);
claims.push(Arc::new(claim));
}
assert!(
job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000
)
.await
.unwrap()
.is_none()
);
claims
}
fn measure_jobs(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
handler: bool,
inputs: Vec<JobInput>,
) -> (u128, u64) {
runtime
.run(async |scope| {
clear(scope, registry).await;
let claims = if handler {
for input in &inputs {
black_box(submit(false, input, scope, registry).await);
}
claims(scope, registry, inputs.len()).await
} else {
Vec::new()
};
let mut checksum = 0u64;
let start = Instant::now();
if handler {
for claim in claims {
execute(generated, black_box(claim), scope, registry)
.await
.unwrap();
checksum += black_box(1);
}
} else {
for input in &inputs {
let id = black_box(submit(generated, black_box(input), scope, registry).await);
checksum += id.len() as u64 + input.name.len() as u64 + input.amount as u64;
}
}
let elapsed = start.elapsed().as_nanos();
check_rows(scope, registry, &inputs, handler).await;
(elapsed, checksum)
})
.unwrap()
}
fn main() {
let (directory, path) = directory("jobs", r#""runtime":{"mode":"worker"}"#);
let runtime = runtime();
let registry = runtime
.run(async |scope| database_registry(&path, scope).await)
.unwrap()
.unwrap();
check_jobs(&runtime, &registry);
header();
for (name, handler) in [("job_enqueue_commit", false), ("job_handler_commit", true)] {
samples(name, |generated, count| {
measure_jobs(&runtime, &registry, generated, handler, inputs(count))
});
}
samples("job_enqueue_large_payload_commit", |generated, count| {
let mut inputs = inputs(count.div_ceil(64));
for input in &mut inputs {
input.name.push_str(&"a".repeat(64 * 1024));
}
measure_jobs(&runtime, &registry, generated, false, inputs)
});
samples("job_submit_claim_complete", |generated, count| {
// 完整循环的每个样本包含 250 项实际任务;初始化及独立 SQL 核验不计时。
measure_cycle(&runtime, &registry, generated, count.div_ceil(8), false)
});
samples("job_backlog_claim_complete", |generated, count| {
measure_cycle(&runtime, &registry, generated, count.div_ceil(8), true)
});
samples("job_enqueue_duplicate_commit", |generated, count| {
measure_duplicate(&runtime, &registry, generated, inputs(count))
});
drop(registry);
drop(runtime);
drop(directory);
}
fn measure_duplicate(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
inputs: Vec<JobInput>,
) -> (u128, u64) {
runtime
.run(async |scope| {
clear(scope, registry).await;
let mut expected = Vec::with_capacity(inputs.len());
for input in &inputs {
expected.push(submit(false, input, scope, registry).await);
}
let mut actual = Vec::with_capacity(inputs.len());
let mut checksum = 0u64;
let start = Instant::now();
for input in &inputs {
let id = submit(generated, black_box(input), scope, registry).await;
checksum += black_box(id.len() as u64);
actual.push(id);
}
let elapsed = start.elapsed().as_nanos();
assert_eq!(actual, expected);
check_rows(scope, registry, &inputs, false).await;
(elapsed, checksum)
})
.unwrap()
}
fn measure_cycle(
runtime: &Runtime,
registry: &Arc<Registry>,
generated: bool,
count: usize,
backlog: bool,
) -> (u128, u64) {
let inputs = inputs(count);
runtime
.run(async |scope| {
clear(scope, registry).await;
if backlog {
for input in &inputs {
submit(generated, input, scope, registry).await;
}
}
let start = Instant::now();
let mut checksum = 0u64;
for input in &inputs {
if !backlog {
submit(generated, black_box(input), scope, registry).await;
}
let claim = {
let mut database = DbContext::new(registry.clone());
let executor = database.on(JOB_BINDING, scope).await.unwrap();
job::claim(
executor,
scope,
dever_runtime::database::now().unwrap(),
60000,
)
.await
.unwrap()
.unwrap()
};
execute(generated, Arc::new(claim), scope, registry)
.await
.unwrap();
checksum += black_box(1);
}
let elapsed = start.elapsed().as_nanos();
check_rows(scope, registry, &inputs, true).await;
(elapsed, checksum)
})
.unwrap()
}
+156
View File
@@ -0,0 +1,156 @@
// 手写 Rust 重新实现 payload 编码、业务计算、行解码和事务边界;仅复用同一队列和数据库设施。
use dever_runtime::{
job::{self, Claim, Execution, Failure as JobFailure},
wire::serde::{Deserialize, Serialize},
};
const SCHEMA: &str = JOB_SCHEMA;
const TARGET: &str = "bench.queue.job.publish";
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct RustPayload {
name: String,
amount: i64,
}
impl<'input> Deserialize<'input> for RustPayload {
fn deserialize<Decoder: dever_runtime::wire::serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
#[derive(Deserialize)]
#[serde(crate = "dever_runtime::wire::serde", deny_unknown_fields)]
struct Fields {
name: String,
amount: i64,
}
// 与 Dever record 一样只接受对象,不能用 serde 默认的数组兼容路径降低校验成本。
let fields = dever_runtime::wire::object::<Fields, Decoder>(decoder)?;
Ok(Self {
name: fields.name,
amount: fields.amount,
})
}
}
struct RustResult {
_id: dever_runtime::database::Id<RESULT_MODEL>,
_created_at: dever_runtime::time::calendar::DateTime,
_name: String,
amount: i64,
}
async fn rust_submit(
name: &str,
amount: i64,
key: &str,
scope: &Scope<'_>,
database: &mut DbContext,
execution: &Execution,
) -> Result<dever_runtime::value::id::Id, dever_runtime::database::Error> {
let payload = dever_runtime::wire::json::Json::encode(&RustPayload {
name: name.to_owned(),
amount,
})
.map_err(|_| {
dever_runtime::database::Error::new(
dever_runtime::database::Kind::InvalidData,
"invalid Job payload",
)
})?;
let now = dever_runtime::database::now()?;
let executor = database.on(JOB_BINDING, scope).await?;
job::enqueue(
executor,
scope,
job::Submission {
target: TARGET,
payload: Some(&payload),
schema: SCHEMA,
execution,
key,
policy: job::Policy {
retries: 2,
timeout_ms: 1000,
},
at: now,
},
now,
)
.await
}
async fn rust_apply(
payload: RustPayload,
scope: &Scope<'_>,
database: &mut DbContext,
) -> Result<(), JobFailure> {
let amount = payload
.amount
.checked_mul(3)
.and_then(|value| value.checked_add(7))
.ok_or(JobFailure::Handler)?;
let parameters = vec![
Parameter::Text(payload.name),
Parameter::Integer(amount),
Parameter::Integer(dever_runtime::database::now().map_err(|_| JobFailure::Handler)?),
];
let executor = database
.on(0, scope)
.await
.map_err(|_| JobFailure::Handler)?;
let saved = executor.query(scope,
"INSERT INTO \"bench_queue_model\" (\"name\",\"amount\",\"created_at\") VALUES (?1,?2,?3) RETURNING \"id\",\"created_at\",\"name\",\"amount\"",
parameters, 1, |row| Ok(RustResult {
_id: dever_runtime::database::Id(row.get(0)?),
_created_at: dever_runtime::time::calendar::DateTime::from_millis(row.get::<_, i64>(1)?).map_err(|_| dever_runtime::database::Error::new(dever_runtime::database::Kind::InvalidData, "invalid stored DateTime"))?,
_name: row.get(2)?, amount: row.get(3)?,
})
).await.map_err(|_| JobFailure::Handler)?.pop().ok_or(JobFailure::Handler)?;
if saved.amount < 0 {
return Err(JobFailure::Handler);
}
Ok(())
}
async fn rust_execute(
binding: usize,
claim: Arc<Claim>,
scope: Scope<'static>,
registry: Arc<Registry>,
) -> Result<(), JobFailure> {
let execution: Execution =
dever_runtime::wire::decode(&claim.execution).map_err(|_| JobFailure::Blocked)?;
if claim.target != TARGET || claim.schema != SCHEMA || !registry.same_pool(binding, JOB_BINDING)
{
return Err(JobFailure::Blocked);
}
if !matches!(execution, Execution::System { tenant: None }) || claim.execution_tenant != 0 {
return Err(JobFailure::Blocked);
}
let payload: RustPayload =
dever_runtime::wire::decode(&claim.payload).map_err(|_| JobFailure::Blocked)?;
let mut database = DbContext::new(registry);
let owner = database.enter();
let outcome = async {
rust_apply(payload, &scope, &mut database).await?;
let executor = database
.on(JOB_BINDING, &scope)
.await
.map_err(|_| JobFailure::Handler)?;
job::complete(
executor,
&scope,
&claim,
dever_runtime::database::now().map_err(|_| JobFailure::Handler)?,
)
.await
.map_err(|_| JobFailure::Handler)
}
.await;
database
.finish(&scope, owner, outcome.is_ok())
.await
.map_err(|_| JobFailure::Handler)?;
outcome
}
+182
View File
@@ -0,0 +1,182 @@
// 两侧共享相同的 wire 库和预算,隔离 Dever 生成代码开销,不代替独立 JSON 引擎对比。
use dever_runtime::wire::{json::Json, serde::{Serialize, Serializer, ser::SerializeStruct}};
fn json_fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo { message: "JSON failure", source: "json-reference", line: 1, column: 1 }
}
struct Payload<'value> { values: &'value [i64] }
impl Serialize for Payload<'_> {
fn serialize<Encoder: Serializer>(&self, encoder: Encoder) -> Result<Encoder::Ok, Encoder::Error> {
let mut record = encoder.serialize_struct("Payload", 1)?;
record.serialize_field("values", self.values)?;
record.end()
}
}
fn generated_encode(values: &[i64]) -> Result<String, Fault> {
GENERATED_ENCODE(values).map_err(json_fault)
}
fn reference_encode(values: &[i64]) -> Result<String, Fault> {
Json::encode(&Payload { values }).map(|value| value.as_str().to_owned()).map_err(json_fault)
}
// 独立 serde 输出不执行 Dever 的三个预算,只作为额外编码成本的下界。
fn reference_serde_encode(values: &[i64]) -> Result<String, Fault> {
dever_runtime::wire::serde_json::to_string(&Payload { values }).map_err(json_fault)
}
// 独立 Rust 的同合同实现:两层容器,根和数组占两个值,i64 最长 20 字节。
// 值数合格时,含分隔符的最大输出不足 16 MiB,无须逐片段再次检查。
fn reference_bounded_encode(values: &[i64]) -> Result<String, Fault> {
if values.len() > dever_runtime::wire::document::MAX_VALUES - 2 {
return Err(json_fault("JSON value budget"));
}
reference_serde_encode(values)
}
fn generated_lookup(values: &[String]) -> Result<i64, Fault> {
values.first().map_or(Ok(-1), |value| GENERATED_LOOKUP(value).map_err(json_fault))
}
fn reference_lookup(values: &[String]) -> Result<i64, Fault> {
let Some(source) = values.first() else { return Ok(-1); };
let json = Json::parse(source).map_err(json_fault)?;
json.property("count").map_err(json_fault)?.map_or(Ok(-1), |value| value.as_integer().map_err(json_fault))
}
fn generated_update(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_UPDATE(value).map_err(json_fault))
}
fn reference_update(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else { return Ok(String::new()); };
let value = Json::parse(source).map_err(json_fault)?;
let child = Json::integer(43).map_err(json_fault)?;
value.set_property("count", &child).map(|value| value.as_str().to_owned()).map_err(json_fault)
}
fn generated_scalar_text(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_SCALAR_TEXT(value).map_err(json_fault))
}
fn reference_scalar_text(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| Json::string(value).map(|value| value.as_str().to_owned()).map_err(json_fault))
}
fn generated_scalar_float(values: &[f64]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_SCALAR_FLOAT(*value).map_err(json_fault))
}
fn reference_scalar_float(values: &[f64]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| Json::float(*value).map(|value| value.as_str().to_owned()).map_err(json_fault))
}
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Fixed<'value> {
text: &'value str,
amount: i64,
ratio: f64,
}
fn reference_fixed_value(text: &str, ratio: f64) -> Result<String, Fault> {
if !ratio.is_finite() { return Err(json_fault("non-finite Float")); }
Json::encode(&Fixed { text, amount: 7, ratio })
.map(Json::into_text).map_err(json_fault)
}
fn generated_fixed(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_FIXED_RECORD(value, 4.25).map_err(json_fault))
}
fn reference_fixed(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| reference_fixed_value(value, 4.25))
}
fn main() {
println!("workload,sample,implementation,nanos,checksum");
for count in [0, 8, 256] {
let input = [Json::parse(&serde_json::to_string(&vec!["雪山 "; count]).unwrap()).unwrap()];
compare(&format!("json_typed_decode_{count}"), &input, 2000, generated_decode, reference_decode, |values| values.iter().map(|value| value.len() as u64).sum());
compare(&format!("json_captured_decode_{count}"), &input, 2000, generated_captured_decode, reference_captured_decode, |value| *value as u64);
}
let invalid = [Json::parse("[1]").unwrap()];
compare("json_captured_decode_invalid", &invalid, 2000, generated_captured_decode, reference_captured_decode, |value| *value as u64);
for state in ["Ready", "Busy"] {
let input = [Json::parse(&format!("\"{state}\"")).unwrap()];
compare(&format!("json_enum_decode_{state}"), &input, 100_000, generated_enum_decode, reference_enum_decode, |value| u64::from(*value));
}
property_costs();
raw_output_costs();
let values = (0..256).collect::<Vec<i64>>();
compare("json_record", &values, 2000, generated_encode, reference_encode, |value| value.len() as u64);
for count in [16, 256, 4096] {
let input = (0..count).collect::<Vec<i64>>();
compare_named(&format!("json_record_serde_{count}"), &input, 2000, generated_encode, reference_serde_encode, |value| value.len() as u64, "rust_serde");
compare_named(&format!("json_record_bounded_{count}"), &input, 2000, generated_encode, reference_bounded_encode, |value| value.len() as u64, "rust_bounded");
}
let maximum = vec![0; dever_runtime::wire::document::MAX_VALUES - 2];
assert_eq!(generated_encode(&maximum).unwrap(), reference_bounded_encode(&maximum).unwrap());
let excessive = vec![0; dever_runtime::wire::document::MAX_VALUES - 1];
assert!(generated_encode(&excessive).is_err());
assert!(reference_bounded_encode(&excessive).is_err());
let source = format!(r#"{{"count":42,"ratio":1e9999,"text":"雪","values":{values:?}}}"#);
compare("json_lookup", std::slice::from_ref(&source), 2000, generated_lookup, reference_lookup, |value| *value as u64);
compare("json_update", &[source], 2000, generated_update, reference_update, |value| value.len() as u64);
compare("json_scalar_text", &["雪\"\\".repeat(512)], 2000, generated_scalar_text, reference_scalar_text, |value| value.len() as u64);
compare("json_scalar_float", &[12345.6789], 2000, generated_scalar_float, reference_scalar_float, |value| value.len() as u64);
for bytes in [64, 4096, 65536] {
let input = ["a".repeat(bytes)];
assert_eq!(generated_fixed(&input).unwrap(), reference_fixed(&input).unwrap());
compare(&format!("json_fixed_record_{bytes}"), &input, 2000, generated_fixed, reference_fixed, |value| value.len() as u64);
}
for ratio in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY] {
assert!(GENERATED_FIXED_RECORD("", ratio).is_err());
assert!(reference_fixed_value("", ratio).is_err());
}
let overhead = reference_fixed_value("", 4.25).unwrap().len();
let maximum = "a".repeat(dever_runtime::wire::document::MAX_BYTES - overhead);
assert_eq!(GENERATED_FIXED_RECORD(&maximum, 4.25).unwrap().len(), dever_runtime::wire::document::MAX_BYTES);
assert!(GENERATED_FIXED_RECORD(&(maximum + "a"), 4.25).is_err());
}
fn generated_decode(values: &[Json]) -> Result<Vec<String>, Fault> {
values.first().map_or(Ok(Vec::new()), |value| GENERATED_DECODE(value).map_err(json_fault))
}
fn reference_decode(values: &[Json]) -> Result<Vec<String>, Fault> {
// Dever 保留解码失败文本;两侧构造错误后再统一映射为基准 Fault。
values.first().map_or(Ok(Vec::new()), |value| serde_json::from_str(value.as_str()).map_err(|error| json_fault(error.to_string())))
}
fn generated_captured_decode(values: &[Json]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| GENERATED_CAPTURED_DECODE(value).map_err(json_fault))
}
fn reference_captured_decode(values: &[Json]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
Ok(match serde_json::from_str::<Vec<String>>(value.as_str()) {
Ok(values) => values.len() as i64,
Err(error) => { let _message = error.to_string(); -1 }
})
})
}
#[derive(dever_runtime::wire::serde::Deserialize)]
#[serde(crate = "dever_runtime::wire::serde")]
enum WireState { Ready, Busy }
fn generated_enum_decode(values: &[Json]) -> Result<bool, Fault> {
values.first().map_or(Ok(false), |value| GENERATED_ENUM_DECODE(value).map_err(json_fault))
}
fn reference_enum_decode(values: &[Json]) -> Result<bool, Fault> {
values.first().map_or(Ok(false), |value| {
serde_json::from_str::<WireState>(value.as_str())
.map(|state| matches!(state, WireState::Ready))
.map_err(|error| json_fault(error.to_string()))
})
}
+135
View File
@@ -0,0 +1,135 @@
use dever_runtime::wire::document;
fn document_fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo { message: "JSON failure", source: "document-reference", line: 1, column: 1 }
}
fn generated_document(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |source| GENERATED_DOCUMENT(source).map_err(document_fault))
}
fn reference_document(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |source| {
let value = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)?;
document::stringify_with_depth(&value, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
})
}
// 独立手写 Map 适配,解析/写出引擎仍共用;只衡量表示转换与生成层。
type ReferenceNode = reference_writer::Node<String, Vec<i64>, Map<String, i64>>;
fn reference_map_document(values: &[String]) -> Result<String, Fault> {
let Some(source) = values.first() else { return Ok(String::new()); };
let parsed = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)?;
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
for (index, value) in parsed.nodes.into_iter().enumerate() {
let value = match value {
document::Value::Null => ReferenceNode::Null,
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
document::Value::Number(value) => ReferenceNode::Number(value),
document::Value::String(value) => ReferenceNode::String(value),
document::Value::Array(values) => ReferenceNode::Array(values),
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
};
nodes.insert(index as i64, value);
}
reference_map_write(parsed.root as i64, &nodes)
}
fn reference_map_write(root: i64, nodes: &indexmap::IndexMap<i64, ReferenceNode>) -> Result<String, Fault> {
let root = usize::try_from(root).map_err(document_fault)?;
if nodes.len() > document::MAX_VALUES { return Err(document_fault("too many nodes")); }
let mut ordered = vec![None; nodes.len()];
for (id, value) in nodes {
if let Ok(index) = usize::try_from(*id) && let Some(slot) = ordered.get_mut(index) {
*slot = Some(value);
}
}
let ordered = ordered.into_iter().map(|value| value.ok_or_else(|| document_fault("missing node"))).collect::<Result<Vec<_>, _>>()?;
reference_writer::stringify_nodes(root, ordered.len(), |index| {
match ordered[index] {
ReferenceNode::Null => reference_writer::Node::Null,
ReferenceNode::Boolean(value) => reference_writer::Node::Boolean(*value),
ReferenceNode::Number(value) => reference_writer::Node::Number(value.as_str()),
ReferenceNode::String(value) => reference_writer::Node::String(value.as_str()),
ReferenceNode::Array(values) => reference_writer::Node::Array(values.iter().copied()),
ReferenceNode::Object(values) => reference_writer::Node::Object(values.iter().map(|(name, id)| (name.as_str(), *id))),
}
}, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
}
#[derive(Debug)]
enum ReferenceFailure {
Fault(Fault),
Parse(i64, String, Fault),
Write(i64, String, Fault),
}
impl std::fmt::Display for ReferenceFailure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fault(fault) | Self::Parse(_, _, fault) | Self::Write(_, _, fault) => fault.fmt(formatter),
}
}
}
fn reference_map_faults(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |source| reference_map_contract(source).map_err(document_fault))
}
// 原参照继续保留;这里独立保留解析/输出错误的载荷与静态源位置。
fn reference_map_contract(source: &str) -> Result<String, ReferenceFailure> {
let parse_fault = &FaultInfo { message: "ParseResult.Failed", source: "document-reference", line: 1, column: 1 };
let write_fault = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 2, column: 1 };
let fail = |node, message: &str| ReferenceFailure::Write(node, message.into(), write_fault);
let parsed = document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
.map_err(|error| ReferenceFailure::Parse(error.position as i64, error.message, parse_fault))?;
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
for (index, value) in parsed.nodes.into_iter().enumerate() {
let value = match value {
document::Value::Null => ReferenceNode::Null,
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
document::Value::Number(value) => ReferenceNode::Number(value),
document::Value::String(value) => ReferenceNode::String(value),
document::Value::Array(values) => ReferenceNode::Array(values),
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
};
nodes.insert(index as i64, value);
}
let root = parsed.root as i64;
let root_index = usize::try_from(root).map_err(|_| fail(root, "negative JSON root"))?;
if nodes.len() > document::MAX_VALUES { return Err(fail(root, "JSON node count exceeds 65536 values")); }
let mut ordered = vec![None; nodes.len()];
for (id, value) in &nodes {
if let Ok(index) = usize::try_from(*id) && let Some(slot) = ordered.get_mut(index) {
*slot = Some(value);
}
}
let ordered = ordered.into_iter().enumerate().map(|(index, value)| value.ok_or_else(|| fail(index as i64, "JSON node IDs must be consecutive"))).collect::<Result<Vec<_>, _>>()?;
reference_writer::stringify_nodes(root_index, ordered.len(), |index| {
match ordered[index] {
ReferenceNode::Null => reference_writer::Node::Null,
ReferenceNode::Boolean(value) => reference_writer::Node::Boolean(*value),
ReferenceNode::Number(value) => reference_writer::Node::Number(value.as_str()),
ReferenceNode::String(value) => reference_writer::Node::String(value.as_str()),
ReferenceNode::Array(values) => reference_writer::Node::Array(values.iter().copied()),
ReferenceNode::Object(values) => reference_writer::Node::Object(values.iter().map(|(name, id)| (name.as_str(), *id))),
}
}, document::DOCUMENT_MAX_DEPTH).map_err(|error| ReferenceFailure::Write(error.node as i64, error.message, write_fault))
}
fn main() {
println!("case,sample,implementation,elapsed_ns,checksum");
let text = "雪\\\"\n".repeat(256);
let strings = dever_runtime::wire::json::Json::encode(&vec![text; 32]).unwrap().as_str().to_owned();
let numbers = format!("[{}]", vec!["1234567890123456789012345678901234e-12"; 256].join(","));
let mut nested = "1e9999".to_owned();
for _ in 0..48 { nested = format!("{{\"value\":{nested}}}"); }
let empty_objects = format!("[{}]", vec!["{}"; 128].join(","));
let wide = format!("{{{}}}", (0..256).map(|index| format!("\"key{index}\":1e9999")).collect::<Vec<_>>().join(","));
for (name, source) in [("document_text", strings), ("document_numbers", numbers), ("document_nested", nested), ("document_empty", "{}".into()), ("document_empty_objects", empty_objects), ("document_wide", wide)] {
compare(name, std::slice::from_ref(&source), 100, generated_document, reference_document, |value| value.bytes().map(u64::from).sum());
compare(&format!("{name}_map"), std::slice::from_ref(&source), 100, generated_document, reference_map_document, |value| value.bytes().map(u64::from).sum());
compare(&format!("{name}_map_faults"), &[source], 100, generated_document, reference_map_faults, |value| value.bytes().map(u64::from).sum());
}
}
+131
View File
@@ -0,0 +1,131 @@
// 阶段诊断复用正式入口;shared 标签只测运行库,不是独立 Rust 引擎参照。
// map 场景名保留历史编号,Rust 参照仍构造 Map;当前 Dever 直接拥有节点 List。
struct PreparedDocument {
generated: DOCUMENT_TYPE,
reference: (i64, indexmap::IndexMap<i64, ReferenceNode>),
raw: document::Document,
}
fn reference_nodes(parsed: document::Document) -> (i64, indexmap::IndexMap<i64, ReferenceNode>) {
let mut nodes = indexmap::IndexMap::with_capacity(parsed.nodes.len());
for (index, value) in parsed.nodes.into_iter().enumerate() {
let value = match value {
document::Value::Null => ReferenceNode::Null,
document::Value::Boolean(value) => ReferenceNode::Boolean(value),
document::Value::Number(value) => ReferenceNode::Number(value),
document::Value::String(value) => ReferenceNode::String(value),
document::Value::Array(values) => ReferenceNode::Array(values),
document::Value::Object(values) => ReferenceNode::Object(values.map_or_else(|| Map::with_capacity(0), |values| *values)),
};
nodes.insert(index as i64, value);
}
(parsed.root as i64, nodes)
}
fn phase_raw_parse(values: &[String]) -> Result<usize, Fault> {
values.first().map_or(Ok(0), |source| {
document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
.map(|parsed| black_box(parsed).nodes.len()).map_err(document_fault)
})
}
fn phase_generated_parse(values: &[String]) -> Result<usize, Fault> {
values.first().map_or(Ok(0), |source| {
GENERATED_PARSE(source).map(|parsed| black_box(parsed).field1.len()).map_err(document_fault)
})
}
fn phase_reference_parse(values: &[String]) -> Result<usize, Fault> {
values.first().map_or(Ok(0), |source| {
document::parse_with_depth(source, document::DOCUMENT_MAX_DEPTH)
.map(|parsed| black_box(reference_nodes(parsed)).1.len()).map_err(document_fault)
})
}
fn phase_generated_write(values: &[PreparedDocument]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_WRITE(&value.generated).map_err(document_fault))
}
fn phase_reference_write(values: &[PreparedDocument]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| reference_map_write(value.reference.0, &value.reference.1))
}
fn phase_raw_write(values: &[PreparedDocument]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
document::stringify_with_depth(&value.raw, document::DOCUMENT_MAX_DEPTH).map_err(document_fault)
})
}
#[derive(Debug)]
enum PhaseWriteFailure {
Fault(Fault),
Write(i64, String, Fault),
}
impl std::fmt::Display for PhaseWriteFailure {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Fault(fault) | Self::Write(_, _, fault) => fault.fmt(formatter),
}
}
}
fn phase_contract_write(values: &[PreparedDocument]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| reference_contract_write(&value.generated).map_err(document_fault))
}
fn phase_adapter_write(values: &[PreparedDocument]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| runtime_contract_write(&value.generated).map_err(document_fault))
}
// 同一份节点直接调用当前运行库,只衡量生成适配,不作为独立 JSON 引擎参照。
fn runtime_contract_write(value: &DOCUMENT_TYPE) -> Result<String, PhaseWriteFailure> {
let origin = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 1, column: 1 };
let root = usize::try_from(value.field0).map_err(|_| {
PhaseWriteFailure::Write(value.field0, "negative JSON root".into(), origin)
})?;
document::stringify_slice(root, &value.field1, document::DOCUMENT_MAX_DEPTH)
.map_err(|error| PhaseWriteFailure::Write(error.node as i64, error.message, origin))
}
// 同一实参及错误布局,只隔离生成层;解析和输出引擎仍共享。
fn reference_contract_write(value: &DOCUMENT_TYPE) -> Result<String, PhaseWriteFailure> {
let origin = &FaultInfo { message: "StringifyResult.Failed", source: "document-reference", line: 1, column: 1 };
let fail = |node, message: &str| PhaseWriteFailure::Write(node, message.into(), origin);
let root = usize::try_from(value.field0).map_err(|_| fail(value.field0, "negative JSON root"))?;
reference_writer::stringify_nodes(root, value.field1.len(), |index| {
match &value.field1[index] {
document::Value::Null => reference_writer::Node::Null,
document::Value::Boolean(value) => reference_writer::Node::Boolean(*value),
document::Value::Number(value) => reference_writer::Node::Number(value.as_str()),
document::Value::String(value) => reference_writer::Node::String(value.as_str()),
document::Value::Array(values) => reference_writer::Node::Array(values.iter().copied()),
document::Value::Object(values) => reference_writer::Node::Object(values.iter().flat_map(|values| values.iter()).map(|(name, id)| (name.as_str(), *id))),
}
}, document::DOCUMENT_MAX_DEPTH).map_err(|error| PhaseWriteFailure::Write(error.node as i64, error.message, origin))
}
fn main() {
println!("case,sample,implementation,elapsed_ns,checksum");
let numbers = format!("[{}]", vec!["1234567890123456789012345678901234e-12"; 256].join(","));
let mut nested = "1e9999".to_owned();
for _ in 0..48 { nested = format!("{{\"value\":{nested}}}"); }
let text = dever_runtime::wire::json::Json::encode(&vec!["雪\\\"\n".repeat(256); 32]).unwrap().as_str().to_owned();
let ascii = dever_runtime::wire::serde_json::to_string(&vec!["abcdef".repeat(256); 32]).unwrap();
let unicode = dever_runtime::wire::serde_json::to_string(&vec!["雪🙂".repeat(256); 32]).unwrap();
for (name, source) in [("numbers", numbers), ("nested", nested), ("text", text), ("ascii", ascii), ("unicode", unicode)] {
let prepared = PreparedDocument {
generated: GENERATED_PARSE(&source).unwrap(),
reference: reference_nodes(document::parse_with_depth(&source, document::DOCUMENT_MAX_DEPTH).unwrap()),
raw: document::parse_with_depth(&source, document::DOCUMENT_MAX_DEPTH).unwrap(),
};
let inputs = std::slice::from_ref(&source);
compare_named(&format!("{name}_parse_shared"), inputs, 100, phase_raw_parse, phase_raw_parse, |count| *count as u64, "shared");
compare(&format!("{name}_parse_map"), inputs, 100, phase_generated_parse, phase_reference_parse, |count| *count as u64);
let values = &[prepared];
compare_named(&format!("{name}_write_shared"), values, 100, phase_raw_write, phase_raw_write, |value| value.bytes().map(u64::from).sum(), "shared");
compare(&format!("{name}_write_map"), values, 100, phase_generated_write, phase_reference_write, |value| value.bytes().map(u64::from).sum());
compare(&format!("{name}_write_map_contract"), values, 100, phase_generated_write, phase_contract_write, |value| value.bytes().map(u64::from).sum());
compare_named(&format!("{name}_write_adapter"), values, 100, phase_generated_write, phase_adapter_write, |value| value.bytes().map(u64::from).sum(), "shared_engine");
}
}
+130
View File
@@ -0,0 +1,130 @@
// 两侧都接收已验证的 JSON,返回独立拥有的原始子树;不在计时内重复验证输入。
// 独立参照只使用 Serde,不调用 Dever 的键比较或字段读取实现。
use dever_runtime::wire::serde::{
Deserializer,
de::{DeserializeSeed, IgnoredAny, MapAccess, Visitor},
};
use dever_runtime::wire::serde_json::{self, value::RawValue};
struct PropertyCase {
source: Json,
name: String,
}
struct MatchingKey<'name>(&'name str);
impl<'input> DeserializeSeed<'input> for MatchingKey<'_> {
type Value = bool;
fn deserialize<Decoder: Deserializer<'input>>(
self,
decoder: Decoder,
) -> Result<bool, Decoder::Error> {
decoder.deserialize_str(self)
}
}
impl<'input> Visitor<'input> for MatchingKey<'_> {
type Value = bool;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("an object key")
}
fn visit_str<Failure: dever_runtime::wire::serde::de::Error>(
self,
value: &str,
) -> Result<bool, Failure> {
Ok(value == self.0)
}
}
struct MatchingProperty<'name>(&'name str);
impl<'input> Visitor<'input> for MatchingProperty<'_> {
type Value = Option<&'input RawValue>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("an object")
}
fn visit_map<Access: MapAccess<'input>>(
self,
mut fields: Access,
) -> Result<Self::Value, Access::Error> {
let mut result = None;
while let Some(matches) = fields.next_key_seed(MatchingKey(self.0))? {
if matches {
result = Some(fields.next_value()?);
} else {
fields.next_value::<IgnoredAny>()?;
}
}
Ok(result)
}
}
fn runtime_property(values: &[PropertyCase]) -> Result<Option<String>, Fault> {
let Some(value) = values.first() else {
return Ok(None);
};
value
.source
.property(&value.name)
.map(|found| found.map(Json::into_text))
.map_err(json_fault)
}
fn native_property(values: &[PropertyCase]) -> Result<Option<String>, Fault> {
let Some(value) = values.first() else {
return Ok(None);
};
let mut decoder =
dever_runtime::wire::serde_json::Deserializer::from_str(value.source.as_str());
decoder
.deserialize_map(MatchingProperty(&value.name))
.map(|found| found.map(|raw| raw.get().to_owned()))
.map_err(json_fault)
}
fn property_costs() {
for (kind, suffix) in [("plain", ""), ("escaped", "雪\\\"\n")] {
let names = (0..256)
.map(|index| format!("key_{index:03}_{suffix}"))
.collect::<Vec<_>>();
let fields = names
.iter()
.map(|name| {
format!(
"{}: [ 1e9999, -0, \"\\u96ea\" ]",
serde_json::to_string(name).unwrap()
)
})
.collect::<Vec<_>>();
let source = format!("{{{}}}", fields.join(","));
for (position, name) in [
("first", names[0].as_str()),
("middle", names[128].as_str()),
("last", names[255].as_str()),
("missing", "absent"),
] {
let values = [PropertyCase {
source: Json::parse(&source).unwrap(),
name: name.into(),
}];
let expected =
(position != "missing").then(|| r#"[ 1e9999, -0, "\u96ea" ]"#.to_owned());
assert_eq!(runtime_property(&values).unwrap(), expected);
assert_eq!(native_property(&values).unwrap(), expected);
compare_named(
&format!("json_property_{kind}_{position}"),
&values,
1000,
runtime_property,
native_property,
|value| value.as_ref().map_or(0, |text| text.len() as u64),
"rust_serde",
);
}
}
}
+264
View File
@@ -0,0 +1,264 @@
// 两侧从已验证的原始值开始,独立参照直接序列化 Serde RawValue。
// 此组只比较原文输出;构造与更新成本由现有 json 组同时检查。
struct RawOutputCase {
dever: Json,
rust: Box<RawValue>,
values: usize,
depth: usize,
}
fn runtime_raw_output(values: &[RawOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
Ok(value.dever.as_str().trim_ascii().to_owned())
})
}
fn native_raw_output(values: &[RawOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
serde_json::to_string(&value.rust).map_err(json_fault)
})
}
fn runtime_nested_output(values: &[RawOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
Json::array(std::slice::from_ref(&value.dever)).map(Json::into_text).map_err(json_fault)
})
}
fn native_nested_output(values: &[RawOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
if value.values + 1 > 65_536 || value.depth + 1 > 64 || value.rust.get().len() + 2 > 16 * 1024 * 1024 {
return Err(json_fault("JSON budget"));
}
serde_json::to_string(&[&value.rust]).map_err(json_fault)
})
}
fn generated_nested_array_output(values: &[RawOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
GENERATED_ARRAY(std::slice::from_ref(&value.dever)).map_err(json_fault)
})
}
fn runtime_from_text(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
Json::parse(value).map(Json::into_text).map_err(json_fault)
})
}
fn native_from_text(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
serde_json::from_str::<Box<RawValue>>(value)
.map(|raw| Box::<str>::from(raw).into()).map_err(json_fault)
})
}
fn runtime_from_text_nested(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
let raw = Json::parse(value).map_err(json_fault)?;
Json::array(&[raw]).map(Json::into_text).map_err(json_fault)
})
}
fn native_from_text_nested(values: &[String]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
let raw: Box<RawValue> = serde_json::from_str(value).map_err(json_fault)?;
serde_json::to_string(&[raw]).map_err(json_fault)
})
}
fn from_text_costs(name: &str, source: &str, values: &[RawOutputCase]) {
let count = if name == "short" { 100_000 } else { 1000 };
compare_named(
&format!("json_nested_output_{name}"), values, count,
runtime_nested_output, native_nested_output,
|value| value.len() as u64, "rust_serde",
);
compare_named(
&format!("json_dever_array_output_{name}"), values, count,
generated_nested_array_output, native_nested_output,
|value| value.len() as u64, "rust_serde",
);
// 原生参照仅检查 JSON 语法,不包含 Dever 的去重和预算,不能据此宣称合同追平。
let sources = [source.to_owned()];
compare_named(
&format!("json_from_text_{name}"), &sources, count,
runtime_from_text, native_from_text,
|value| value.len() as u64, "rust_syntax_only",
);
compare_named(
&format!("json_from_text_nested_{name}"), &sources, count,
runtime_from_text_nested, native_from_text_nested,
|value| value.len() as u64, "rust_syntax_only",
);
}
fn raw_output_costs() {
nested_output_costs();
array_output_costs();
for (name, text) in [
("short", "a".repeat(64)),
("plain_4096", "a".repeat(4096)),
("plain_65536", "a".repeat(65536)),
("escaped", "雪\"\\\n".repeat(1024)),
] {
let source = format!(
"{{\"text\":{},\"number\":1e9999}}",
serde_json::to_string(&text).unwrap()
);
let values = [RawOutputCase {
dever: Json::parse(&source).unwrap(),
rust: serde_json::from_str(&source).unwrap(),
// 上面构造的对象恰好包含 text、number 两个标量字段。
values: 3,
depth: 1,
}];
assert_eq!(runtime_raw_output(&values).unwrap(), source);
assert_eq!(native_raw_output(&values).unwrap(), source);
from_text_costs(name, &source, &values);
compare_named(
&format!("json_raw_output_{name}"),
&values,
1000,
runtime_raw_output,
native_raw_output,
|value| value.len() as u64,
"rust_serde",
);
// 输入解码已经取得 RawValue;输出不应丢掉它后再从 String 解析。
let decoded = [RawOutputCase {
dever: serde_json::from_str(&source).unwrap(),
rust: serde_json::from_str(&source).unwrap(),
values: 3,
depth: 1,
}];
compare_named(
&format!("json_decoded_output_{name}"),
&decoded,
1000,
runtime_raw_output,
native_raw_output,
|value| value.len() as u64,
"rust_serde",
);
}
}
struct ArrayOutputCase {
dever: Vec<Json>,
rust: Vec<NativeArrayValue>,
}
struct NativeArrayValue {
raw: Box<RawValue>,
values: usize,
depth: usize,
}
struct NativeArray<'value>(&'value [NativeArrayValue]);
impl Serialize for NativeArray<'_> {
fn serialize<Encoder: Serializer>(&self, encoder: Encoder) -> Result<Encoder::Ok, Encoder::Error> {
use dever_runtime::wire::serde::ser::{Error, SerializeSeq};
let mut sequence = encoder.serialize_seq(Some(self.0.len()))?;
let mut count = 1;
let mut bytes = 2;
for (index, value) in self.0.iter().enumerate() {
count += value.values;
bytes += value.raw.get().len() + usize::from(index != 0);
if count > 65_536 || value.depth + 1 > 64 || bytes > 16 * 1024 * 1024 {
return Err(Encoder::Error::custom("JSON budget"));
}
sequence.serialize_element(&value.raw)?;
}
sequence.end()
}
}
fn runtime_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
Json::array(&value.dever).map(Json::into_text).map_err(json_fault)
})
}
fn native_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
serde_json::to_string(&NativeArray(&value.rust)).map_err(json_fault)
})
}
fn generated_array_output(values: &[ArrayOutputCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| {
GENERATED_ARRAY(&value.dever).map_err(json_fault)
})
}
fn array_output_costs() {
for count in [0, 1, 4, 16] {
let sources: Vec<_> = [" null ", " [1,-0] ", r#" {"雪":"\u96ea"} "#, " [] "]
.into_iter().cycle().take(count).collect();
// 两侧每个子值都已验证且保留结构统计;计时包含合并预算和完整写出。
let values = [ArrayOutputCase {
dever: sources.iter().map(|source| Json::parse(source).unwrap()).collect(),
rust: sources.iter().map(|source| {
let (values, depth) = reference_shape(&serde_json::from_str(source).unwrap());
NativeArrayValue { raw: serde_json::from_str(source).unwrap(), values, depth }
}).collect(),
}];
compare_named(&format!("json_array_mixed_{count}"), &values, 100_000,
runtime_array_output, native_array_output, |value| value.len() as u64, "rust_bounded");
compare_named(&format!("json_dever_array_mixed_{count}"), &values, 100_000,
generated_array_output, native_array_output, |value| value.len() as u64, "rust_bounded");
}
}
struct NestedCase {
dever: Json,
rust: Box<RawValue>,
values: usize,
depth: usize,
}
fn runtime_nested_record(values: &[NestedCase]) -> Result<String, Fault> {
values.first().map_or(Ok(String::new()), |value| GENERATED_NESTED(&value.dever).map_err(json_fault))
}
fn native_nested_record(values: &[NestedCase]) -> Result<String, Fault> {
#[derive(Serialize)]
#[serde(crate = "dever_runtime::wire::serde")]
struct Nested<'value> { value: &'value RawValue }
let Some(value) = values.first() else { return Ok(String::new()); };
if value.values + 1 > 65_536 || value.depth + 1 > 64 || value.rust.get().len() + 10 > 16 * 1024 * 1024 {
return Err(json_fault("JSON budget"));
}
serde_json::to_string(&Nested { value: &value.rust }).map_err(json_fault)
}
fn reference_shape(value: &serde_json::Value) -> (usize, usize) {
let children: Vec<_> = match value {
serde_json::Value::Array(values) => values.iter().map(reference_shape).collect(),
serde_json::Value::Object(values) => values.values().map(reference_shape).collect(),
_ => return (1, 0),
};
(1 + children.iter().map(|child| child.0).sum::<usize>(), 1 + children.iter().map(|child| child.1).max().unwrap_or(0))
}
fn nested_output_costs() {
for (name, text) in [
("short", "a".repeat(64)),
("plain_4096", "a".repeat(4096)),
("plain_65536", "a".repeat(65536)),
("escaped", "雪\"\\\n".repeat(1024)),
] {
let source = format!("{{\"text\":{},\"number\":-0}}", serde_json::to_string(&text).unwrap());
// 两侧都从已验证原文及结构信息开始;独立 Rust 不调用 Dever 的验证或输出。
let (count, depth) = reference_shape(&serde_json::from_str(&source).unwrap());
let values = [NestedCase {
dever: Json::parse(&source).unwrap(),
rust: serde_json::from_str(&source).unwrap(), values: count, depth,
}];
compare_named(&format!("json_dever_nested_{name}"), &values,
if name == "short" { 100_000 } else { 1000 },
runtime_nested_record, native_nested_record, |value| value.len() as u64, "rust_bounded");
}
}
+208
View File
@@ -0,0 +1,208 @@
//! 冻结原写出算法供前后对照;生产运行库不依赖此通用节点适配。
use dever_runtime::wire::document::{MAX_BYTES, MAX_VALUES, WriteError};
use dever_runtime::wire::serde_json;
pub enum Node<Text, Children, Members> {
Null,
Boolean(bool),
Number(Text),
String(Text),
Array(Children),
Object(Members),
}
#[derive(Clone, Copy)]
struct Size {
bytes: usize,
values: usize,
depth: usize,
}
/// 节点直接按下标借用,编号和文档结构在 measure 中检查;不再给取节点包装错误通道。
pub fn stringify_nodes<'node, Children, Members>(
root: usize,
count: usize,
node: impl Fn(usize) -> Node<&'node str, Children, Members>,
max_depth: usize,
) -> Result<String, WriteError>
where
Children: Iterator<Item = i64>,
Members: Iterator<Item = (&'node str, i64)>,
{
let bytes = measure(root, count, &node, max_depth)?;
let mut output = String::with_capacity(bytes);
write_node(&node, root, &mut output);
Ok(output)
}
fn measure<'node, Children, Members>(
root: usize,
count: usize,
node: &impl Fn(usize) -> Node<&'node str, Children, Members>,
max_depth: usize,
) -> Result<usize, WriteError>
where
Children: Iterator<Item = i64>,
Members: Iterator<Item = (&'node str, i64)>,
{
let fail = |node, message: &str| WriteError {
node,
message: message.into(),
};
if count > MAX_VALUES {
return Err(fail(root, "JSON node count exceeds 65536 values"));
}
if root >= count {
return Err(fail(root, "JSON root does not refer to a document node"));
}
let mut sizes = Vec::<Size>::with_capacity(count);
for index in 0..count {
let mut size = Size {
bytes: 0,
values: 1,
depth: 0,
};
match node(index) {
Node::Null => size.bytes = 4,
Node::Boolean(value) => size.bytes = if value { 4 } else { 5 },
Node::Number(raw) => {
if !matches!(raw.as_bytes().first(), Some(b'-' | b'0'..=b'9'))
|| !serde_json::from_str::<&serde_json::value::RawValue>(raw)
.is_ok_and(|value| value.get() == raw)
{
return Err(fail(index, "invalid JSON number"));
}
size.bytes = raw.len();
}
Node::String(text) => size.bytes = string_size(text),
Node::Array(children) => {
size.bytes = 2;
size.depth = 1;
for (position, child) in children.enumerate() {
size.bytes += usize::from(position != 0);
add_child(&mut size, child, &sizes, index, max_depth)?;
}
}
Node::Object(fields) => {
size.bytes = 2;
size.depth = 1;
for (position, (name, child)) in fields.enumerate() {
size.bytes += string_size(name) + 1 + usize::from(position != 0);
add_child(&mut size, child, &sizes, index, max_depth)?;
}
}
}
check(size, index, max_depth)?;
sizes.push(size);
}
Ok(sizes[root].bytes)
}
fn add_child(
size: &mut Size,
child: i64,
sizes: &[Size],
node: usize,
max_depth: usize,
) -> Result<(), WriteError> {
let child = usize::try_from(child).map_err(|_| WriteError {
node,
message: "negative JSON node ID".into(),
})?;
let child = sizes.get(child).ok_or_else(|| WriteError {
node,
message: "JSON child must precede parent".into(),
})?;
size.bytes += child.bytes;
size.values += child.values;
size.depth = size.depth.max(child.depth + 1);
check(*size, node, max_depth)
}
fn check(size: Size, node: usize, max_depth: usize) -> Result<(), WriteError> {
let message = if size.bytes > MAX_BYTES {
"JSON exceeds 16 MiB"
} else if size.values > MAX_VALUES {
"JSON contains more than 65536 values"
} else if size.depth > max_depth {
"JSON nesting exceeds configured limit"
} else {
return Ok(());
};
Err(WriteError {
node,
message: message.into(),
})
}
fn string_size(text: &str) -> usize {
text.len()
+ 2
+ text
.bytes()
.map(|byte| match byte {
b'"' | b'\\' | 8 | 12 | b'\n' | b'\r' | b'\t' => 1,
0..=31 => 5,
_ => 0,
})
.sum::<usize>()
}
fn write_node<'node, Children, Members>(
node: &impl Fn(usize) -> Node<&'node str, Children, Members>,
index: usize,
output: &mut String,
) where
Children: Iterator<Item = i64>,
Members: Iterator<Item = (&'node str, i64)>,
{
match node(index) {
Node::Null => output.push_str("null"),
Node::Boolean(value) => output.push_str(if value { "true" } else { "false" }),
Node::Number(raw) => output.push_str(raw),
Node::String(text) => write_string(text, output),
Node::Array(children) => {
output.push('[');
for (position, child) in children.enumerate() {
if position != 0 {
output.push(',');
}
write_node(node, child as usize, output);
}
output.push(']');
}
Node::Object(fields) => {
output.push('{');
for (position, (name, child)) in fields.enumerate() {
if position != 0 {
output.push(',');
}
write_string(name, output);
output.push(':');
write_node(node, child as usize, output);
}
output.push('}');
}
}
}
fn write_string(text: &str, output: &mut String) {
output.push('"');
for character in text.chars() {
match character {
'"' => output.push_str("\\\""),
'\\' => output.push_str("\\\\"),
'\u{08}' => output.push_str("\\b"),
'\u{0c}' => output.push_str("\\f"),
'\n' => output.push_str("\\n"),
'\r' => output.push_str("\\r"),
'\t' => output.push_str("\\t"),
character if character < '\u{20}' => {
use std::fmt::Write;
write!(output, "\\u{:04x}", character as u32).unwrap();
}
character => output.push(character),
}
}
output.push('"');
}
+51
View File
@@ -0,0 +1,51 @@
use std::hint::black_box;
use std::time::{Duration, Instant};
#[inline(never)]
fn generated_list(values: &[i64]) -> Result<i64, Fault> {
GENERATED_LIST(values)
}
#[inline(never)]
fn reference_list(values: &[i64]) -> Result<i64, Fault> {
let failure = &FaultInfo { source: "reference", line: 1, column: 1, message: "integer overflow" };
values.iter().try_fold(0i64, |sum, value| sum.checked_add(*value)).ok_or(failure)
}
#[inline(never)]
fn measure(operation: fn(&[i64]) -> Result<i64, Fault>, values: &[i64], count: usize) -> (Duration, usize) {
let operation = black_box(operation);
let start = Instant::now();
let mut checksum = 0usize;
for _ in 0..count {
checksum = checksum.wrapping_add(black_box(operation(black_box(values)).unwrap()) as usize);
}
(start.elapsed(), checksum)
}
fn main() {
let values: Vec<i64> = (0..1024).map(|position| (position * 37 % 100) as i64).collect();
for values in [values.as_slice(), &[], &[i64::MAX, 1], &[i64::MIN, -1], &[i64::MAX, -1]] {
assert_eq!(generated_list(values).map_err(|_| ()), reference_list(values).map_err(|_| ()));
}
let expected = generated_list(&values).unwrap() as usize * 100_000;
measure(generated_list, &values, 10_000);
measure(reference_list, &values, 10_000);
println!("case,sample,implementation,elapsed_ns,checksum");
for iteration in 0..21 {
for control in if iteration % 2 == 0 { [false, true] } else { [true, false] } {
let case = if control { "list_same_function" } else { "list_shared_caller" };
let mut results = Vec::new();
for first in if iteration % 2 == 0 { [true, false] } else { [false, true] } {
let operation = if first || control { generated_list } else { reference_list };
let (elapsed, checksum) = measure(operation, &values, 100_000);
assert_eq!(checksum, expected);
let implementation = if first { "dever" } else if control { "same" } else { "rust" };
results.push((implementation, elapsed, checksum));
}
for (implementation, elapsed, checksum) in results {
println!("{case},{iteration},{implementation},{},{checksum}", elapsed.as_nanos());
}
}
}
}
+355
View File
@@ -0,0 +1,355 @@
use indexmap::IndexMap;
fn map_fault() -> Fault {
&FaultInfo {
message: "integer overflow",
source: "map-reference",
line: 1,
column: 1,
}
}
fn add(total: i64, value: i64) -> Result<i64, Fault> {
total.checked_add(value).ok_or_else(map_fault)
}
fn build_text(values: &[String]) -> IndexMap<String, String> {
let mut map = IndexMap::with_capacity(values.len());
for value in values {
map.insert(value.clone(), value.clone());
}
map
}
fn build_nested(values: &[String]) -> IndexMap<String, Vec<String>> {
let mut map = IndexMap::with_capacity(values.len());
for value in values {
map.insert(value.clone(), vec![value.clone(), value.clone()]);
}
map
}
fn text_weight(key: &str, value: &str) -> Result<i64, Fault> {
add(key.chars().count() as i64, value.chars().count() as i64)
}
fn nested_weight(key: &str, values: &[String]) -> Result<i64, Fault> {
let count = values
.iter()
.try_fold(0, |total, value| add(total, value.chars().count() as i64))?;
add(key.chars().count() as i64, count)
}
fn owned_text(values: &[String]) -> Result<i64, Fault> {
build_text(values)
.into_iter()
.try_fold(0, |total, (key, value)| {
add(total, text_weight(&key, &value)?)
})
}
fn owned_nested(values: &[String]) -> Result<i64, Fault> {
build_nested(values)
.into_iter()
.try_fold(0, |total, (key, value)| {
add(total, nested_weight(&key, &value)?)
})
}
struct Borrowed {
text: Map<String, String>,
nested: Map<String, Vec<String>>,
reference_text: IndexMap<String, String>,
reference_nested: IndexMap<String, Vec<String>>,
}
fn borrowed_text(values: &[Borrowed]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| GENERATED_BORROWED_TEXT(&value.text))
}
fn borrowed_nested(values: &[Borrowed]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| GENERATED_BORROWED_NESTED(&value.nested))
}
fn reference_borrowed_text(values: &[Borrowed]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
value
.reference_text
.iter()
.try_fold(0, |total, (key, value)| {
add(total, text_weight(key, value)?)
})
})
}
// 原始参照合并了两个错误来源;分别保留故障位置,并用同一张表排除输入地址差异。
fn reference_text_same_faults<const SHARED: bool>(values: &[Borrowed]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
let mut entries = if SHARED {
value.text.iter()
} else {
value.reference_text.iter()
};
entries.try_fold(0i64, |total, (key, value)| {
let count = (key.chars().count() as i64)
.checked_add(value.chars().count() as i64)
.ok_or(const {
&FaultInfo {
message: "integer arithmetic fault",
source: "map-reference",
line: 1,
column: 1,
}
})?;
total.checked_add(count).ok_or(const {
&FaultInfo {
message: "integer overflow",
source: "map-reference",
line: 2,
column: 1,
}
})
})
})
}
fn reference_borrowed_nested(values: &[Borrowed]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
value
.reference_nested
.iter()
.try_fold(0, |total, (key, value)| {
add(total, nested_weight(key, value)?)
})
})
}
fn reference_nested_same_order(values: &[Borrowed]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
value
.reference_nested
.iter()
.try_fold(0, |total, (key, values)| {
let key_count = key.chars().count() as i64;
let count = values
.iter()
.try_fold(0, |total, value| add(total, value.chars().count() as i64))?;
add(total, add(key_count, count)?)
})
})
}
fn reference_nested_same_faults<const SHARED: bool>(values: &[Borrowed]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
let mut entries = if SHARED {
value.nested.iter()
} else {
value.reference_nested.iter()
};
entries.try_fold(0i64, |total, (key, values)| {
let key_count = key.chars().count() as i64;
let count = values.iter().try_fold(0i64, |total, value| {
total.checked_add(value.chars().count() as i64).ok_or(
const {
&FaultInfo {
message: "integer overflow",
source: "map-reference",
line: 1,
column: 1,
}
},
)
})?;
let count = key_count.checked_add(count).ok_or(
const {
&FaultInfo {
message: "integer arithmetic fault",
source: "map-reference",
line: 2,
column: 1,
}
},
)?;
total.checked_add(count).ok_or(
const {
&FaultInfo {
message: "integer overflow",
source: "map-reference",
line: 3,
column: 1,
}
},
)
})
})
}
fn list_first(values: &[String]) -> Result<Option<String>, Fault> {
values
.first()
.map_or(Ok(None), |value| GENERATED_LIST_FIRST(value))
}
// 两个实现逐个读取同一批表,避免把某一份输入的分配位置固定给一个标签。
fn nested_corpus<const GENERATED: bool>(values: &[Borrowed]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let input = std::slice::from_ref(value);
let count = if GENERATED {
borrowed_nested(input)?
} else {
reference_nested_same_faults::<true>(input)?
};
total = add(total, count)?;
}
Ok(total)
}
// 已知字面量的选取等价于复制输入,独立 Rust 直接实现这一结果。
fn reference_owned_text(values: &[String]) -> Result<Option<String>, Fault> {
values.first().map_or(Ok(None), |value| {
let value = value.as_str();
Ok(Some(value.to_owned()))
})
}
fn map_get(values: &[String]) -> Result<Option<String>, Fault> {
values
.first()
.map_or(Ok(None), |value| GENERATED_MAP_GET(value))
}
fn main() {
println!("case,sample,implementation,elapsed_ns,checksum");
let reference_first = std::env::var_os("DEVER_MAP_REFERENCE_FIRST").is_some();
for size in [32, 4096] {
let values: Vec<String> = (0..64)
.map(|index| format!("{index}:{}", "雪".repeat(size / 3)))
.collect();
// 两种建表顺序都计时;同表参照另行保留,排除独立分配地址的影响。
let (text, nested, reference_text, reference_nested) = if reference_first {
let reference_text = build_text(&values);
let reference_nested = build_nested(&values);
(GENERATED_BUILD_TEXT(&values).unwrap(), GENERATED_BUILD_NESTED(&values).unwrap(), reference_text, reference_nested)
} else {
(GENERATED_BUILD_TEXT(&values).unwrap(), GENERATED_BUILD_NESTED(&values).unwrap(), build_text(&values), build_nested(&values))
};
let maps = [Borrowed { text, nested, reference_text, reference_nested }];
selection_comparisons(size);
compare(
&format!("map_text_owned_{size}"),
&values,
200,
GENERATED_OWNED_TEXT,
owned_text,
|value| *value as u64,
);
compare(
&format!("map_text_borrowed_{size}"),
&maps,
200,
borrowed_text,
reference_borrowed_text,
|value| *value as u64,
);
compare(
&format!("map_text_same_faults_{size}"),
&maps,
200,
borrowed_text,
reference_text_same_faults::<false>,
|value| *value as u64,
);
compare(
&format!("map_text_same_table_{size}"),
&maps,
200,
borrowed_text,
reference_text_same_faults::<true>,
|value| *value as u64,
);
compare(
&format!("map_nested_owned_{size}"),
&values,
200,
GENERATED_OWNED_NESTED,
owned_nested,
|value| *value as u64,
);
compare(
&format!("map_nested_borrowed_{size}"),
&maps,
200,
borrowed_nested,
reference_borrowed_nested,
|value| *value as u64,
);
compare(
&format!("list_first_owned_{size}"),
&values,
2000,
list_first,
reference_owned_text,
|value| value.as_ref().map_or(0, |text| text.len() as u64),
);
compare(
&format!("map_get_owned_{size}"),
&values,
2000,
map_get,
reference_owned_text,
|value| value.as_ref().map_or(0, |text| text.len() as u64),
);
compare(
&format!("map_nested_same_order_{size}"),
&maps,
200,
borrowed_nested,
reference_nested_same_order,
|value| *value as u64,
);
compare(
&format!("map_nested_same_faults_{size}"),
&maps,
200,
borrowed_nested,
reference_nested_same_faults::<false>,
|value| *value as u64,
);
compare(
&format!("map_nested_same_table_{size}"),
&maps,
200,
borrowed_nested,
reference_nested_same_faults::<true>,
|value| *value as u64,
);
let mut corpus: Vec<_> = (0..4)
.map(|offset| {
let mut input = values.clone();
input.rotate_left(offset * 13);
Borrowed {
text: Map::with_capacity(0),
nested: GENERATED_BUILD_NESTED(&input).unwrap(),
reference_text: IndexMap::new(),
reference_nested: IndexMap::new(),
}
})
.collect();
for reverse in [false, true] {
if reverse { corpus.reverse(); }
compare(
&format!("map_nested_corpus_{size}_{}", if reverse { "reverse" } else { "forward" }),
&corpus,
50,
nested_corpus::<true>,
nested_corpus::<false>,
|value| *value as u64,
);
}
}
}
+145
View File
@@ -0,0 +1,145 @@
// 独立 Rust 直接遍历 IndexMap;只复制命中项,保持结果顺序和拥有值语义。
struct Selection {
generated: Map<String, String>,
reference: IndexMap<String, String>,
target: String,
}
fn selected_generated(input: &Selection) -> Result<Vec<(String, String)>, Fault> {
GENERATED_MAP_FILTER(&input.generated, &input.target)
}
fn selected_reference(input: &Selection) -> Result<Vec<(String, String)>, Fault> {
let mut output = Vec::new();
for (key, value) in &input.reference {
if *value == input.target {
output.push((key.clone(), value.clone()));
}
}
Ok(output)
}
fn found_generated(input: &Selection) -> Result<Option<(String, String)>, Fault> {
GENERATED_MAP_FIND(&input.generated, &input.target)
}
fn found_reference(input: &Selection) -> Result<Option<(String, String)>, Fault> {
for (key, value) in &input.reference {
if *value == input.target {
return Ok(Some((key.clone(), value.clone())));
}
}
Ok(None)
}
// 使用相同物理表与参数类型,单独检查生成层;独立 IndexMap 参照仍保留。
fn selected_same_table(input: &Selection) -> Result<Vec<(String, String)>, Fault> {
let mut output = Vec::new();
let target = input.target.as_str();
for (key, value) in input.generated.iter() {
if value == target {
output.push((key.clone(), value.clone()));
}
}
Ok(output)
}
fn found_same_table(input: &Selection) -> Result<Option<(String, String)>, Fault> {
let target = input.target.as_str();
for (key, value) in input.generated.iter() {
if value == target {
return Ok(Some((key.clone(), value.clone())));
}
}
Ok(None)
}
fn selection_comparisons(size: usize) {
let reference_first = std::env::var_os("DEVER_MAP_REFERENCE_FIRST").is_some();
for mode in ["last", "first", "missing", "all"] {
let suffix = "雪".repeat(size / 3);
let target = format!(
"{}:{suffix}",
match mode {
"last" => 63,
"missing" => 64,
_ => 0,
}
);
let (mut generated, mut reference) = if reference_first {
let reference = IndexMap::with_capacity(64);
(Map::with_capacity(64), reference)
} else {
(Map::with_capacity(64), IndexMap::with_capacity(64))
};
for index in 0..64 {
let key = format!("{index}:{suffix}");
let value = if mode == "all" {
target.clone()
} else {
key.clone()
};
if reference_first {
reference.insert(key.clone(), value.clone());
generated.insert(key, value);
} else {
generated.insert(key.clone(), value.clone());
reference.insert(key, value);
}
}
let values = Selection {
generated,
reference,
target,
};
// 直接传入表和目标,空表断言复用同一入口,不额外包单元素数组。
let empty = Selection {
generated: Map::with_capacity(0),
reference: IndexMap::new(),
target: String::new(),
};
if mode != "first" {
for (suffix, reference) in [
("", selected_reference as fn(&Selection) -> _),
("_same_table", selected_same_table as fn(&Selection) -> _),
] {
compare_output::<false, _, _>(
&format!("map_filter_{mode}_{size}{suffix}"),
&values,
&empty,
500,
selected_generated,
reference,
|entries| {
entries
.iter()
.map(|(key, value)| (key.len() + value.len()) as u64)
.sum()
},
"rust",
);
}
}
if mode != "all" {
for (suffix, reference) in [
("", found_reference as fn(&Selection) -> _),
("_same_table", found_same_table as fn(&Selection) -> _),
] {
compare_output::<false, _, _>(
&format!("map_find_{mode}_{size}{suffix}"),
&values,
&empty,
500,
found_generated,
reference,
|entry| {
entry
.as_ref()
.map_or(0, |(key, value)| (key.len() + value.len()) as u64)
},
"rust",
);
}
}
}
}
+64
View File
@@ -0,0 +1,64 @@
#include <assert.h>
#include <errno.h>
#include <stdint.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
/* 校准探针:正常释放、失败 realloc、零大小释放和已知的 37 字节残留。 */
static void *volatile retained;
static pthread_barrier_t start;
static void exercise(void) {
for (int index = 0; index < 1000; index++) {
void *first = malloc(10);
assert(first);
first = realloc(first, 20);
assert(first);
void *second = calloc(2, 16);
assert(second);
volatile size_t impossible = SIZE_MAX;
assert(realloc(first, impossible) == NULL);
free(second);
assert(realloc(first, 0) == NULL);
void *aligned = NULL;
assert(posix_memalign(&aligned, 64, 96) == 0);
assert((uintptr_t)aligned % 64 == 0);
void *original = aligned;
errno = E2BIG;
assert(posix_memalign(&aligned, 3, 96) == EINVAL);
assert(aligned == original && errno == E2BIG);
assert(posix_memalign(&aligned, 64, impossible) == ENOMEM);
assert(aligned == original && errno == E2BIG);
free(aligned);
}
}
static void *worker(void *input) {
pthread_barrier_wait(&start);
free(input);
exercise();
return NULL;
}
int main(int count, char **arguments) {
int concurrent = count > 1 && strcmp(arguments[1], "threads") == 0;
if (concurrent) {
pthread_t workers[4];
assert(pthread_barrier_init(&start, NULL, 5) == 0);
for (size_t index = 0; index < 4; index++) {
void *input = malloc(16);
assert(input);
assert(pthread_create(&workers[index], NULL, worker, input) == 0);
}
pthread_barrier_wait(&start);
for (size_t index = 0; index < 4; index++) {
assert(pthread_join(workers[index], NULL) == 0);
}
assert(pthread_barrier_destroy(&start) == 0);
} else {
exercise();
}
if (count > (concurrent ? 2 : 1)) retained = malloc(37);
return 0;
}
+117
View File
@@ -0,0 +1,117 @@
#define _GNU_SOURCE
#include <errno.h>
#include <stdint.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/* glibc 验收探针,不进入运行库;插桩耗时与 RSS 不用于性能比较。
* 只跟踪这些入口实际返回的请求字节,忽略 loader 内部产生的未知指针。
* 固定表不调用分配器,表满直接使验收失败,不能悄悄漏记。 */
extern void *__libc_malloc(size_t size);
extern void *__libc_calloc(size_t count, size_t size);
extern void *__libc_realloc(void *pointer, size_t size);
extern void __libc_free(void *pointer);
extern void *__libc_memalign(size_t alignment, size_t size);
#define CAPACITY 131072
#define DELETED ((uintptr_t)1)
static struct { uintptr_t address; size_t size; } entries[CAPACITY];
static size_t allocations, releases, allocated_bytes, released_bytes, live_bytes, peak_bytes;
static int recording = 1;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static size_t slot(uintptr_t address, int insert) {
size_t first_deleted = CAPACITY;
size_t start = (address >> 4) % CAPACITY;
for (size_t step = 0; step < CAPACITY; step++) {
size_t index = (start + step) % CAPACITY;
if (entries[index].address == address) return index;
if (entries[index].address == DELETED && first_deleted == CAPACITY) first_deleted = index;
if (!entries[index].address) return insert && first_deleted != CAPACITY ? first_deleted : index;
}
if (insert && first_deleted != CAPACITY) return first_deleted;
abort();
}
static void added(void *pointer, size_t size) {
if (!recording || !pointer) return;
uintptr_t address = (uintptr_t)pointer;
size_t index = slot(address, 1);
if (entries[index].address == address) abort();
entries[index].address = address;
entries[index].size = size;
allocations++;
allocated_bytes += size;
live_bytes += size;
if (live_bytes > peak_bytes) peak_bytes = live_bytes;
}
static void removed(uintptr_t address) {
if (!recording || !address) return;
size_t index = slot(address, 0);
if (entries[index].address != address) return;
releases++;
released_bytes += entries[index].size;
live_bytes -= entries[index].size;
entries[index].address = DELETED;
}
void *malloc(size_t size) {
pthread_mutex_lock(&lock);
void *pointer = __libc_malloc(size);
added(pointer, size);
pthread_mutex_unlock(&lock);
return pointer;
}
void *calloc(size_t count, size_t size) {
pthread_mutex_lock(&lock);
void *pointer = __libc_calloc(count, size);
if (pointer) added(pointer, count * size);
pthread_mutex_unlock(&lock);
return pointer;
}
void *realloc(void *pointer, size_t size) {
pthread_mutex_lock(&lock);
uintptr_t address = (uintptr_t)pointer;
void *result = __libc_realloc(pointer, size);
/* 失败时旧块仍存活;glibc 的零大小 realloc 则释放旧块。 */
if (result || size == 0) {
removed(address);
added(result, size);
}
pthread_mutex_unlock(&lock);
return result;
}
int posix_memalign(void **result, size_t alignment, size_t size) {
if (alignment < sizeof(void *) || (alignment & (alignment - 1))) return EINVAL;
int previous_errno = errno;
pthread_mutex_lock(&lock);
void *pointer = __libc_memalign(alignment, size);
if (pointer) {
*result = pointer;
added(pointer, size);
}
pthread_mutex_unlock(&lock);
errno = previous_errno;
return pointer ? 0 : ENOMEM;
}
void free(void *pointer) {
pthread_mutex_lock(&lock);
removed((uintptr_t)pointer);
__libc_free(pointer);
pthread_mutex_unlock(&lock);
}
__attribute__((destructor)) static void report(void) {
pthread_mutex_lock(&lock);
recording = 0;
pthread_mutex_unlock(&lock);
fprintf(stderr, "heap_stats,%zu,%zu,%zu,%zu,%zu,%zu\n",
allocations, releases, allocated_bytes, released_bytes, live_bytes, peak_bytes);
}
+27
View File
@@ -0,0 +1,27 @@
// 只编入 --memory 产物,复用原输入与两侧函数;普通计时产物不改变。
// 全进程初始化和输入也会被探针计数,零次调用作为相同场景的基线。
fn compare_output<const BORROW: bool, Input: ?Sized, Output: PartialEq + std::fmt::Debug>(name: &str, values: &Input, _: &Input, _: usize, generated: fn(&Input) -> Result<Output, Fault>, reference: fn(&Input) -> Result<Output, Fault>, score: fn(&Output) -> u64, reference_name: &str) {
let selected = std::env::var("DEVER_HEAP_CASE").expect("DEVER_HEAP_CASE");
if selected != name { return; }
let implementation = std::env::var("DEVER_HEAP_IMPL").expect("DEVER_HEAP_IMPL");
let count = std::env::var("DEVER_HEAP_COUNT").expect("DEVER_HEAP_COUNT").parse::<usize>().unwrap();
let operation = if implementation == "dever" { generated } else {
assert_eq!(implementation, reference_name);
reference
};
let operation = black_box(operation);
let mut checksum = 0u64;
for _ in 0..count {
let value = black_box(operation(black_box(values)).unwrap());
checksum = checksum.wrapping_add(black_box(score(&value)));
drop(value);
}
println!("heap,{name},{implementation},{count},{checksum}");
// RSS 单独在未加载分配探针的进程读取,避免把探针静态表算进语言成本。
if std::env::var_os("DEVER_HEAP_RSS").is_some() {
let status = std::fs::read_to_string("/proc/self/status").unwrap();
for line in status.lines().filter(|line| line.starts_with("VmRSS:") || line.starts_with("VmHWM:")) {
println!("heap_rss,{line}");
}
}
}
+79
View File
@@ -0,0 +1,79 @@
// 独立 Rust 参照直接使用相同版本的 dec,逐项清理和检查状态。
// 两侧使用同一输入、Decimal128 精度、舍入和顺序,不用二进制浮点代替。
fn generated(values: &[Decimal]) -> Result<Decimal, Fault> {
GENERATED_DECIMAL_SUM(values)
}
fn reference(values: &[Decimal]) -> Result<Decimal, Fault> {
let mut context = dec::Context::<dec::Decimal128>::default();
let mut total = dec::Decimal128::ZERO;
for value in values {
context.clear_status();
total = context.add(total, dec::Decimal128::from_be_bytes(value.to_be_bytes()));
if !total.is_finite() || context.status().overflow() || context.status().invalid_operation()
{
return Err(&FaultInfo {
message: "Decimal fault",
source: "Rust reference",
line: 0,
column: 0,
});
}
}
Ok(Decimal::from_be_bytes(total.to_be_bytes()))
}
fn score(value: &Decimal) -> u64 {
u64::from_be_bytes(value.to_be_bytes()[8..].try_into().unwrap())
}
fn main() {
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
let zero = Decimal::from_int(0);
for values in [
vec![maximum, maximum, -maximum],
vec![-maximum, -maximum, maximum],
vec![zero],
Vec::new(),
] {
let actual = generated(&values);
let expected = reference(&values);
assert_eq!(actual.is_err(), expected.is_err());
if let (Ok(actual), Ok(expected)) = (actual, expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
}
let short = [
"0.1", "0.2", "-0.05", "1.25", "-0.00", "10", "-2.50", "0.001",
]
.map(|text| Decimal::parse(text).unwrap());
let integers = (0..256).map(Decimal::from_int).collect::<Vec<_>>();
let palette = [
"-1234567890123456789012345678901234",
"1.000000000000000000000000000000001",
"-0.001",
"-1E-6176",
"-0.00",
"0E+100",
"1E-6176",
"0.9999999999999999999999999999999999",
"1234567890123456789012345678901234",
]
.map(|text| Decimal::parse(text).unwrap());
let mixed = (0..256)
.map(|index| palette[index % palette.len()])
.collect::<Vec<_>>();
println!("case,sample,implementation,elapsed_ns,checksum");
for (name, values, count) in [
("decimal_sum_short", short.as_slice(), 10000),
("decimal_sum_integers", integers.as_slice(), 1000),
("decimal_sum_mixed", mixed.as_slice(), 1000),
] {
assert_eq!(
generated(values).unwrap().to_be_bytes(),
reference(values).unwrap().to_be_bytes()
);
compare(name, values, count, generated, reference, score);
}
}
+203
View File
@@ -0,0 +1,203 @@
// 保留直接 Rust 参照,分别观察值参数、借用参数和 Result 对循环优化的影响。
fn generated(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_FLOAT(value)?;
}
Ok(total)
}
fn reference(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
}
Ok(total)
}
fn generated_batch(values: &[Option<f64>]) -> Result<i64, Fault> {
GENERATED_FLOAT_TOTAL(values)
}
fn reference_batch(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let projected = match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
total = total.checked_add(projected).ok_or(&FaultInfo {
message: "integer overflow",
source: "Rust reference",
line: 0,
column: 0,
})?;
}
Ok(total)
}
fn classify(value: Option<f64>) -> i64 {
match value {
None => -1,
Some(value) if value == 1.0 => 1,
Some(value) if value == 0.0 => 0,
Some(_) => 2,
}
}
fn classify_result(value: Option<f64>) -> Result<i64, Fault> {
Ok(classify(value))
}
fn classify_borrowed(value: &Option<f64>) -> i64 {
match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
}
}
fn value_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify(*value);
}
Ok(total)
}
fn result_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify_result(*value)?;
}
Ok(total)
}
fn borrowed_helper(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += classify_borrowed(value);
}
Ok(total)
}
fn main() {
let special = [
None,
Some(0.0),
Some(-0.0),
Some(1.0),
Some(-1.0),
Some(f64::NAN),
Some(f64::INFINITY),
Some(f64::NEG_INFINITY),
];
let values = (0..256)
.map(|index| special[index % special.len()])
.collect::<Vec<_>>();
let mut state = 0x123456789abcdef0u64;
let mixed = (0..256)
.map(|index| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
if index % 3 == 0 {
Some(f64::from_bits(state))
} else {
special[((state >> 32) as usize) % special.len()]
}
})
.collect::<Vec<_>>();
let references = [
(
"direct",
reference as fn(&[Option<f64>]) -> Result<i64, Fault>,
),
("value", value_helper),
("result", result_helper),
("borrowed", borrowed_helper),
];
for (name, operation) in references {
for value in values.iter().chain(&mixed) {
assert_eq!(generated(&[*value]).unwrap(), operation(&[*value]).unwrap());
}
eprintln!(
"{name}_equals_generated={}",
std::ptr::fn_addr_eq(
operation,
generated as fn(&[Option<f64>]) -> Result<i64, Fault>
)
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
for (name, values) in [("float_batch", &values), ("float_batch_mixed", &mixed)] {
compare(
name,
values,
10000,
generated_batch,
reference_batch,
|value| *value as u64,
);
}
compare(
"float_direct",
&values,
10000,
generated,
reference,
|value| *value as u64,
);
compare(
"float_mixed",
&mixed,
10000,
generated,
reference,
|value| *value as u64,
);
compare(
"float_value_helper",
&values,
10000,
generated,
value_helper,
|value| *value as u64,
);
compare(
"float_result_helper",
&values,
10000,
generated,
result_helper,
|value| *value as u64,
);
compare(
"float_borrowed_helper",
&values,
10000,
generated,
borrowed_helper,
|value| *value as u64,
);
// 同一混合序列也经过各参照,避免把函数边界或布局差异归给 Dever。
for (name, operation) in [
(
"float_mixed_value_helper",
value_helper as fn(&[Option<f64>]) -> Result<i64, Fault>,
),
("float_mixed_result_helper", result_helper),
("float_mixed_borrowed_helper", borrowed_helper),
] {
compare(name, &mixed, 10000, generated, operation, |value| {
*value as u64
});
}
}
+104
View File
@@ -0,0 +1,104 @@
fn decimal_generated(values: &[Option<Decimal>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_DECIMAL(value)?;
}
Ok(total)
}
fn decimal_reference(values: &[Option<Decimal>]) -> Result<i64, Fault> {
let mut total = 0i64;
let zero = Decimal::from_int(0);
for value in values {
total += match value {
None => 2,
Some(value) if *value < zero => -1,
Some(value) if *value == zero => 0,
Some(_) => 1,
};
}
Ok(total)
}
fn edge_generated(values: &[Decimal]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_EDGE(*value)?;
}
Ok(total)
}
fn edge_reference(values: &[Decimal]) -> Result<i64, Fault> {
let mut total = 0i64;
let one = Decimal::from_int(1);
for value in values {
total += i64::from(*value > one);
}
Ok(total)
}
fn float_generated(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += GENERATED_FLOAT(value)?;
}
Ok(total)
}
fn float_reference(values: &[Option<f64>]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total += match value {
None => -1,
Some(value) if *value == 1.0 => 1,
Some(value) if *value == 0.0 => 0,
Some(_) => 2,
};
}
Ok(total)
}
fn main() {
let decimals = (0..256).map(|index| match index % 4 {
0 => None,
1 => Some(Decimal::from_int(-(index as i64))),
2 => Some(Decimal::from_int(0)),
_ => Some(Decimal::from_int(index as i64)),
}).collect::<Vec<_>>();
let edges = ["0", "1", "1.000000000000000000000000000000001", "2"];
let edges = (0..256).map(|index| Decimal::parse(edges[index % 4]).unwrap()).collect::<Vec<_>>();
let special = [None, Some(0.0), Some(-0.0), Some(1.0), Some(-1.0), Some(f64::NAN), Some(f64::INFINITY), Some(f64::NEG_INFINITY)];
let floats = (0..256).map(|index| special[index % special.len()]).collect::<Vec<_>>();
for value in &decimals { assert_eq!(GENERATED_DECIMAL(value).unwrap(), decimal_reference(&[*value]).unwrap()); }
for value in &edges { assert_eq!(GENERATED_EDGE(*value).unwrap(), edge_reference(&[*value]).unwrap()); }
for value in &floats { assert_eq!(GENERATED_FLOAT(value).unwrap(), float_reference(&[*value]).unwrap()); }
println!("case,sample,implementation,elapsed_ns,checksum");
compare("decimal_clauses", &decimals, 10000, decimal_generated, decimal_reference, |value| *value as u64);
compare("decimal_adjacent", &edges, 10000, edge_generated, edge_reference, |value| *value as u64);
compare("float_clauses", &floats, 10000, float_generated, float_reference, |value| *value as u64);
// 独立保留原三组;混合输入覆盖非整数、不同小数位、负零和指数极限。
let spellings = [
"-9.999999999999999999999999999999999E+6144",
"-1234567890123456789012345678901234",
"-1.000000000000000000000000000000001",
"-1.00", "-0.001", "-1E-6176", "-0.00", "0", "0E+100",
"1E-6176", "0.001", "0.9999999999999999999999999999999999",
"1", "1.000", "1.000000000000000000000000000000001",
"1234567890123456789012345678901234",
"9.999999999999999999999999999999999E+6144",
];
let palette = spellings.map(|text| Decimal::parse(text).unwrap());
let mut state = 0x123456789abcdef0u64;
let mixed = (0..256).map(|_| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1);
palette[((state >> 32) as usize) % palette.len()]
}).collect::<Vec<_>>();
let nullable = mixed.iter().enumerate().map(|(index, value)| {
if index % 5 == 0 { None } else { Some(*value) }
}).collect::<Vec<_>>();
for value in &nullable { assert_eq!(GENERATED_DECIMAL(value).unwrap(), decimal_reference(&[*value]).unwrap()); }
for value in &mixed { assert_eq!(GENERATED_EDGE(*value).unwrap(), edge_reference(&[*value]).unwrap()); }
compare("decimal_mixed_clauses", &nullable, 10000, decimal_generated, decimal_reference, |value| *value as u64);
compare("decimal_mixed_adjacent", &mixed, 10000, edge_generated, edge_reference, |value| *value as u64);
}
+309
View File
@@ -0,0 +1,309 @@
// 算术参照直接调用 dec,逐步检查同样的错误;不降低精度或重排运算。
use dec::{Context, Decimal128};
const TWO: Decimal128 = Decimal128::from_ne_bytes(
u128::from_be_bytes([34, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]).to_ne_bytes(),
);
// 两套表示在计时前准备,Rust 循环不经过 Dever 的输入/输出转换。
struct Inputs<Generated, Native> {
generated: Vec<Generated>,
native: Vec<Native>,
}
fn inputs<Generated: Copy, Native>(
values: &[Generated],
convert: impl FnMut(Generated) -> Native,
) -> [Inputs<Generated, Native>; 1] {
[Inputs {
generated: values.to_vec(),
native: values.iter().copied().map(convert).collect(),
}]
}
fn native_bytes(value: Decimal128) -> [u8; 16] {
u128::from_ne_bytes(value.to_ne_bytes()).to_be_bytes()
}
fn raw(value: Decimal) -> Decimal128 {
Decimal128::from_be_bytes(value.to_be_bytes())
}
fn checked(value: Decimal128, context: &Context<Decimal128>) -> Result<Decimal128, Fault> {
if value.is_finite() && !context.status().overflow() && !context.status().invalid_operation() {
Ok(value)
} else {
Err(&FaultInfo {
message: "Decimal arithmetic fault",
source: "Rust reference",
line: 0,
column: 0,
})
}
}
fn operation<const KIND: usize>(
context: &mut Context<Decimal128>,
left: Decimal128,
right: Decimal128,
) -> Result<Decimal128, Fault> {
context.clear_status();
let value = match KIND {
0 => context.add(left, right),
1 => context.sub(left, right),
2 => context.mul(left, right),
3 => context.div(left, right),
4 => {
let product = operation::<2>(context, left, right)?;
let added = operation::<0>(context, product, left)?;
let divided = operation::<3>(context, added, right)?;
return operation::<1>(context, divided, left);
}
5 => context.mul(left, TWO),
_ => unreachable!(),
};
checked(value, context)
}
fn generated_one<const KIND: usize>(left: Decimal, right: Decimal) -> Result<Decimal, Fault> {
match KIND {
0 => GENERATED_ADD(left, right),
1 => GENERATED_SUBTRACT(left, right),
2 => GENERATED_MULTIPLY(left, right),
3 => GENERATED_DIVIDE(left, right),
4 => GENERATED_CALCULATE(left, right),
5 => GENERATED_SCALE(left),
_ => unreachable!(),
}
}
fn score(bytes: [u8; 16]) -> u64 {
u64::from_be_bytes(bytes[8..].try_into().unwrap())
}
fn generated<const KIND: usize>(
inputs: &[Inputs<(Decimal, Decimal), (Decimal128, Decimal128)>],
) -> Result<u64, Fault> {
let values = inputs.first().map_or(&[][..], |input| input.generated.as_slice());
let mut total = 0u64;
for (left, right) in values {
total = total.wrapping_add(score(generated_one::<KIND>(*left, *right)?.to_be_bytes()));
}
Ok(total)
}
fn reference<const KIND: usize>(
inputs: &[Inputs<(Decimal, Decimal), (Decimal128, Decimal128)>],
) -> Result<u64, Fault> {
let values = inputs.first().map_or(&[][..], |input| input.native.as_slice());
let mut context = Context::<Decimal128>::default();
let mut total = 0u64;
for (left, right) in values {
let value = operation::<KIND>(&mut context, *left, *right)?;
total = total.wrapping_add(score(native_bytes(value)));
}
Ok(total)
}
fn compare_operation<const KIND: usize>(name: &str, values: &[(Decimal, Decimal)]) {
let mut context = Context::<Decimal128>::default();
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
for (left, right) in values
.iter()
.copied()
.chain([(maximum, maximum), (maximum, Decimal::from_int(0))])
{
let actual = generated_one::<KIND>(left, right);
let expected = operation::<KIND>(&mut context, raw(left), raw(right));
assert_eq!(actual.is_err(), expected.is_err());
if let (Ok(actual), Ok(expected)) = (actual, expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
}
let inputs = inputs(values, |(left, right)| (raw(left), raw(right)));
compare(
name,
&inputs,
1000,
generated::<KIND>,
reference::<KIND>,
|value| *value,
);
}
fn projected(inputs: &[Inputs<Decimal, Decimal128>]) -> Result<[u8; 16], Fault> {
let values = inputs.first().map_or(&[][..], |input| input.generated.as_slice());
GENERATED_SCALED(values).map(Decimal::to_be_bytes)
}
fn projected_reference(inputs: &[Inputs<Decimal, Decimal128>]) -> Result<[u8; 16], Fault> {
let values = inputs.first().map_or(&[][..], |input| input.native.as_slice());
let mut context = Context::<Decimal128>::default();
let mut total = Decimal128::ZERO;
for value in values {
let next = operation::<5>(&mut context, *value, Decimal128::ZERO)?;
total = operation::<0>(&mut context, total, next)?;
}
Ok(native_bytes(total))
}
fn converted(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
total = total.wrapping_add(score(GENERATED_CONVERT(*value)?.to_be_bytes()));
}
Ok(total)
}
fn converted_reference(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
let decimal = if let Ok(narrow) = i32::try_from(*value) {
Decimal128::from(narrow)
} else if let Ok(narrow) = u32::try_from(*value) {
Decimal128::from(narrow)
} else {
Decimal128::from(*value)
};
total = total.wrapping_add(score(native_bytes(decimal)));
}
Ok(total)
}
fn converted_direct_reference(values: &[i64]) -> Result<u64, Fault> {
let mut total = 0u64;
for value in values {
total = total.wrapping_add(score(native_bytes(Decimal128::from(*value))));
}
Ok(total)
}
fn main() {
let constants = GENERATED_CONSTANTS().unwrap();
let expected = [
Decimal128::ZERO,
Context::<Decimal128>::default().parse("-0.00").unwrap(),
Decimal128::from(i64::MIN),
Decimal128::from(i64::MAX),
Decimal128::from(3i64),
];
assert_eq!(constants.len(), expected.len());
for (actual, expected) in constants.iter().zip(expected) {
assert_eq!(actual.to_be_bytes(), expected.to_be_bytes());
}
let left = [
"-0.00",
"1.25",
"-7.5",
"1E-60",
"9999999999999999999999999999999999",
"-123.456",
"0.9999999999999999999999999999999999",
"1234567.890",
]
.map(|text| Decimal::parse(text).unwrap());
let right = [
"3.25",
"-2",
"0.1",
"1.000000000000000000000000000000001",
"2",
"10",
"-3",
"1",
]
.map(|text| Decimal::parse(text).unwrap());
let values = (0..256)
.map(|index| (left[index % left.len()], right[index % right.len()]))
.collect::<Vec<_>>();
println!("case,sample,implementation,elapsed_ns,checksum");
compare_operation::<0>("decimal_add", &values);
compare_operation::<1>("decimal_subtract", &values);
compare_operation::<2>("decimal_multiply", &values);
compare_operation::<3>("decimal_divide", &values);
compare_operation::<4>("decimal_calculate", &values);
compare_operation::<5>("decimal_literal_scale", &values);
let items = values.iter().map(|(left, _)| *left).collect::<Vec<_>>();
let prepared = inputs(&items, raw);
assert_eq!(projected(&prepared).unwrap(), projected_reference(&prepared).unwrap());
let large = Decimal::parse("4E+6144").unwrap();
let maximum = Decimal::parse("9.999999999999999999999999999999999E+6144").unwrap();
for items in [vec![], vec![large, large, maximum], vec![maximum, large]] {
let prepared = inputs(&items, raw);
let actual = projected(&prepared);
let expected = projected_reference(&prepared);
assert_eq!(actual.is_err(), expected.is_err());
match (actual, expected) {
(Ok(actual), Ok(expected)) => assert_eq!(actual, expected),
(Err(actual), Err(_)) => {
let source = std::fs::read_to_string(actual.source).unwrap();
let needle = if items[0] == maximum {
"value * 2"
} else {
"sum(scale, values)"
};
assert_eq!(
actual.line,
source[..source.find(needle).unwrap()]
.bytes()
.filter(|byte| *byte == b'\n')
.count()
+ 1
);
}
_ => unreachable!(),
}
}
compare(
"decimal_projected_sum",
&prepared,
1000,
projected,
projected_reference,
|value| score(*value),
);
let numbers = [
i64::MIN,
i64::MAX,
-1,
0,
1,
i32::MIN as i64,
i32::MAX as i64,
1234567890123456789,
];
for value in numbers {
assert_eq!(
GENERATED_CONVERT(value).unwrap().to_be_bytes(),
Decimal128::from(value).to_be_bytes()
);
}
let integers = (0..256)
.map(|index| numbers[index % numbers.len()])
.collect::<Vec<_>>();
compare(
"decimal_int_conversion",
&integers,
1000,
converted,
converted_reference,
|value| *value,
);
for (name, numbers) in [
("decimal_int_conversion_narrow", [
i64::from(i32::MIN), -1, 0, 1, i64::from(i32::MAX),
i64::from(i32::MAX) + 1, i64::from(u32::MAX) - 1, i64::from(u32::MAX),
]),
("decimal_int_conversion_wide", [
i64::MIN, i64::MAX, i64::from(i32::MIN) - 1, i64::from(u32::MAX) + 1,
-1234567890123456789, 1234567890123456789, -4294967296, 4294967296,
]),
] {
let values = (0..256).map(|index| numbers[index % numbers.len()]).collect::<Vec<_>>();
compare(name, &values, 1000, converted, converted_reference, |value| *value);
if name.ends_with("wide") {
compare_named("decimal_int_conversion_wide_direct", &values, 1000, converted, converted_direct_reference, |value| *value, "rust_direct");
}
}
}
+69
View File
@@ -0,0 +1,69 @@
fn reference_ports(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let fault = &FaultInfo { message: "integer arithmetic fault", source: "Rust reference", line: 0, column: 0 };
let mut total = 0i64;
for value in values {
let value = value.field0.checked_add(1).ok_or(fault)?;
total = total.checked_add(value).ok_or(fault)?;
}
Ok(total)
}
// 两处溢出保留各自来源;原单一错误参照仍用于观察诊断语义的成本。
fn reference_ports_distinct_faults(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let adjusted = value.field0.checked_add(1).ok_or(&FaultInfo {
message: "integer arithmetic fault", source: "Rust reference", line: 1, column: 1,
})?;
total = total.checked_add(adjusted).ok_or(&FaultInfo {
message: "integer overflow", source: "Rust reference", line: 2, column: 1,
})?;
}
Ok(total)
}
// Port 只允许 1..=65535,加一不可能溢出;累计仍按 Int 合同检查。
fn reference_ports_bounded(values: &[PORT_TYPE]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total = total.checked_add(value.field0.wrapping_add(1)).ok_or(&FaultInfo {
message: "integer overflow", source: "Rust reference", line: 2, column: 1,
})?;
}
Ok(total)
}
const SCALE: [u8; 16] = [34, 7, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0];
fn reference_rates(values: &[RATE_TYPE]) -> Result<Decimal, Fault> {
let fault = &FaultInfo { message: "decimal arithmetic fault", source: "Rust reference", line: 0, column: 0 };
let scale = dec::Decimal128::from_be_bytes(SCALE);
let mut context = dec::Context::<dec::Decimal128>::default();
let mut total = dec::Decimal128::ZERO;
for value in values {
context.clear_status();
let value = context.mul(dec::Decimal128::from_be_bytes(value.field0.to_be_bytes()), scale);
if !value.is_finite() || context.status().overflow() || context.status().invalid_operation() { return Err(fault); }
context.clear_status();
total = context.add(total, value);
if !total.is_finite() || context.status().overflow() || context.status().invalid_operation() { return Err(fault); }
}
Ok(Decimal::from_be_bytes(total.to_be_bytes()))
}
fn main() {
assert_eq!(Decimal::parse("100.0").unwrap().to_be_bytes(), SCALE);
for values in [vec![], vec![1], vec![65535], vec![1, 65535], vec![32768, 32767]] {
let ports = values.into_iter().map(|field0| PORT_TYPE { field0 }).collect::<Vec<_>>();
assert_eq!(GENERATED_TOTAL(&ports).map_err(|fault| fault.message), reference_ports_distinct_faults(&ports).map_err(|fault| fault.message));
assert_eq!(GENERATED_TOTAL(&ports).map_err(|fault| fault.message), reference_ports_bounded(&ports).map_err(|fault| fault.message));
}
println!("case,sample,implementation,elapsed_ns,checksum");
let ports = (0..64).map(|index| PORT_TYPE { field0: index * 1000 + 1 }).collect::<Vec<_>>();
compare("range_int", &ports, 100_000, GENERATED_TOTAL, reference_ports, |value| *value as u64);
compare("range_int_distinct_faults", &ports, 100_000, GENERATED_TOTAL, reference_ports_distinct_faults, |value| *value as u64);
compare("range_int_bounded", &ports, 100_000, GENERATED_TOTAL, reference_ports_bounded, |value| *value as u64);
let rates = (0..32).map(|index| RATE_TYPE { field0: Decimal::parse(&format!("0.{index:02}")).unwrap() }).collect::<Vec<_>>();
assert_eq!(GENERATED_RATES(&rates).unwrap().to_be_bytes(), reference_rates(&rates).unwrap().to_be_bytes());
compare("range_decimal", &rates, 5_000, GENERATED_RATES, reference_rates, |value| u64::from(value.to_be_bytes()[15]));
}
+256
View File
@@ -0,0 +1,256 @@
// 标准库入口来自实际 Dever 源码;参照只使用 dec,不调用 Dever 运行库。
use dec::{Context, Decimal128};
use std::sync::LazyLock;
static NATIVE_CONTEXT: LazyLock<Context<Decimal128>> = LazyLock::new(Context::default);
struct DecimalInput {
text: String,
value: Decimal,
native: Decimal128,
places: i64,
}
fn native_parse(text: &str) -> Option<Decimal128> {
let mut context = NATIVE_CONTEXT.clone();
let value = context.parse(text).ok()?;
(value.is_finite() && !context.status().inexact()).then_some(value)
}
fn parsed<const NATIVE: bool>(values: &[String]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
values
.iter()
.map(|text| {
Ok(if NATIVE {
native_parse(text).map(|value| value.to_be_bytes())
} else {
DECIMAL_PARSE(text)?.map(Decimal::to_be_bytes)
})
})
.collect()
}
struct NativeDisplay(Decimal128);
impl std::fmt::Display for NativeDisplay {
fn fmt(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value = Context::<Decimal128>::default().reduce(self.0);
if value.is_zero() {
output.write_str("0")
} else {
write!(output, "{value}")
}
}
}
fn formatted<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<String>, Fault> {
values
.iter()
.map(|input| {
if NATIVE {
Ok(NativeDisplay(input.native).to_string())
} else {
DECIMAL_TEXT(input.value)
}
})
.collect()
}
fn native_round(value: Decimal128, places: i64) -> Option<Decimal128> {
let exponent = i32::try_from(places.checked_neg()?).ok()?;
if !(-6176..=6111).contains(&exponent) {
return None;
}
let mut context = NATIVE_CONTEXT.clone();
let mut quantum = Decimal128::ONE;
context.set_exponent(&mut quantum, exponent);
let value = context.quantize(value, quantum);
(value.is_finite() && !context.status().underflow() && !context.status().invalid_context())
.then_some(value)
}
fn rounded<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
native_round(input.native, input.places).map(|value| value.to_be_bytes())
} else {
DECIMAL_ROUND(input.value, input.places)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
fn negated<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<[u8; 16]>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
(-input.native).to_be_bytes()
} else {
(-input.value).to_be_bytes()
}
})
.collect())
}
fn compared<const NATIVE: bool>(
values: &[DecimalInput],
) -> Result<Vec<(bool, Option<std::cmp::Ordering>)>, Fault> {
Ok(values
.windows(2)
.map(|pair| {
if NATIVE {
(
pair[0].native == pair[1].native,
pair[0].native.partial_cmp(&pair[1].native),
)
} else {
(
pair[0].value == pair[1].value,
pair[0].value.partial_cmp(&pair[1].value),
)
}
})
.collect())
}
fn byte_score(values: &[Option<[u8; 16]>]) -> u64 {
values
.iter()
.flatten()
.flat_map(|value| value.iter())
.map(|value| u64::from(*value))
.sum()
}
fn text_score(values: &[String]) -> u64 {
values
.iter()
.flat_map(|value| value.bytes())
.map(u64::from)
.sum()
}
fn main() {
println!("case,sample,implementation,nanoseconds,checksum");
let spellings = [
"0",
"-0",
"1.2300",
"-12.345",
"1E-6176",
"1E6111",
"9.999999999999999999999999999999999E6144",
"1234567890123456789012345678901234",
"1.25",
"1.35",
"-250",
"+.125",
"1e-34",
"1.000000000000000000000000000000000",
];
let texts = spellings
.iter()
.cycle()
.take(112)
.map(|text| (*text).to_owned())
.collect::<Vec<_>>();
let inputs = texts
.iter()
.enumerate()
.map(|(index, text)| DecimalInput {
text: text.clone(),
value: Decimal::parse(text).unwrap(),
native: native_parse(text).unwrap(),
places: [0, 1, 2, -2, 6176, -6111][index % 6],
})
.collect::<Vec<_>>();
compare(
"decimal_parse_valid",
&texts,
1000,
parsed::<false>,
parsed::<true>,
|value| byte_score(value),
);
let invalid = [
"",
"+",
"NaN",
"-sNaN",
"Infinity",
"nan123",
" 1",
"1 ",
"1_000",
"1e",
"1\0",
"雪",
"1e9999",
"1e-9999",
"12345678901234567890123456789012345",
]
.iter()
.cycle()
.take(120)
.map(|value| (*value).to_owned())
.collect::<Vec<_>>();
assert!(
parsed::<false>(&invalid)
.unwrap()
.iter()
.all(Option::is_none)
);
compare(
"decimal_parse_invalid",
&invalid,
1000,
parsed::<false>,
parsed::<true>,
|value| byte_score(value),
);
compare(
"decimal_format",
&inputs,
1000,
formatted::<false>,
formatted::<true>,
|value| text_score(value),
);
compare(
"decimal_round",
&inputs,
1000,
rounded::<false>,
rounded::<true>,
|value| byte_score(value),
);
compare(
"decimal_negate",
&inputs,
1000,
negated::<false>,
negated::<true>,
|value| value.iter().flatten().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_compare",
&inputs,
1000,
compared::<false>,
compared::<true>,
|value| {
value
.iter()
.map(|(equal, order)| {
u64::from(*equal) + u64::from(order == &Some(std::cmp::Ordering::Less))
})
.sum()
},
);
wire_cases(&inputs);
storage_cases();
}
+284
View File
@@ -0,0 +1,284 @@
// 在基准中编译同一份私有 PostgreSQL codec,不为测试扩大生产 API。
use dever_runtime::value;
mod database {
pub use dever_runtime::database::{Error, Kind};
pub mod scalar {
pub use super::{Error, Kind};
#[path = "DECIMAL_SCALAR_SOURCE"]
pub mod decimal;
}
pub mod codec {
#[path = "DECIMAL_CODEC_SOURCE"]
mod implementation;
pub fn encode(value: crate::Decimal, fixed: bool, output: &mut bytes::BytesMut) {
if fixed {
implementation::encode(value, 34, 4, output).unwrap();
} else {
implementation::encode_native(value, output);
}
}
pub fn decode(bytes: &[u8]) -> crate::Decimal {
implementation::decode(bytes).unwrap()
}
}
}
use database::codec as postgres_codec;
struct StoredDecimal {
value: Decimal,
native: Decimal128,
key: String,
postgres: Vec<u8>,
}
fn native_parts(coefficient: i128, exponent: i32) -> Option<Decimal128> {
let mut context = NATIVE_CONTEXT.clone();
// i128 的六次乘加以及 String 收缩均更慢;借用路径复用 CString 的容量特化。
let value = context
.parse(format!("{coefficient}E{exponent}").as_str())
.ok()?;
(value.is_finite() && !context.status().inexact()).then_some(value)
}
fn native_scaled(value: Decimal128, precision: u32, scale: u32) -> Option<i128> {
if !(1..=34).contains(&precision) || scale > precision {
return None;
}
let coefficient = value.coefficient();
let shift = value.exponent() + scale as i32;
let scaled = if coefficient == 0 {
0
} else if shift >= 0 {
coefficient.checked_mul(10_i128.checked_pow(shift as u32)?)?
} else {
let divisor = 10_i128.checked_pow(shift.unsigned_abs())?;
if coefficient % divisor != 0 {
return None;
}
coefficient / divisor
};
(scaled.unsigned_abs() < 10_u128.pow(precision)).then_some(scaled)
}
fn sqlite_encoded<const NATIVE: bool>(values: &[StoredDecimal]) -> Result<Vec<String>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
format!(
"{:035}",
native_scaled(input.native, 34, 4).unwrap() + 10_i128.pow(34)
)
} else {
dever_runtime::database::scalar::decimal::encode(input.value, 34, 4).unwrap()
}
})
.collect())
}
fn sqlite_decoded<const NATIVE: bool>(
values: &[StoredDecimal],
) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
let text = &input.key;
if text.len() != 35 || !text.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
let shifted = text.parse::<i128>().ok()?;
if shifted <= 0 || shifted >= 2 * 10_i128.pow(34) {
return None;
}
native_parts(shifted - 10_i128.pow(34), -4).map(|value| value.to_be_bytes())
} else {
dever_runtime::database::scalar::decimal::decode(&input.key, 34, 4)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
// 独立按协议生成分组;两侧均用栈数组,不给参照添加额外分配。
fn native_postgres(coefficient: i128, exponent: i32, scale: u16, output: &mut bytes::BytesMut) {
use bytes::BufMut;
let mut number = coefficient.unsigned_abs() * 10_u128.pow(exponent.rem_euclid(4) as u32);
let mut groups = [0u16; 10];
let mut count = 0;
while number != 0 {
groups[count] = (number % 10000) as u16;
count += 1;
number /= 10000;
}
let weight = exponent.div_euclid(4) + count as i32 - 1;
let skip = groups[..count]
.iter()
.take_while(|value| **value == 0)
.count();
output.put_i16((count - skip) as i16);
output.put_i16(if count == 0 { 0 } else { weight as i16 });
output.put_u16(if coefficient < 0 { 0x4000 } else { 0 });
output.put_u16(scale);
for value in groups[skip..count].iter().rev() {
output.put_u16(*value);
}
}
fn native_postgres_decode(bytes: &[u8]) -> Option<Decimal128> {
if bytes.len() < 8 || !bytes.len().is_multiple_of(2) {
return None;
}
let word = |index: usize| i16::from_be_bytes([bytes[index * 2], bytes[index * 2 + 1]]);
let count = word(0);
let weight = word(1);
let sign = word(2);
if count < 0
|| bytes.len() != 8 + count as usize * 2
|| !matches!(sign, 0 | 0x4000)
|| word(3) < 0
{
return None;
}
let mut coefficient = 0i128;
for index in 0..count as usize {
let digit = word(index + 4);
if !(0..10000).contains(&digit) {
return None;
}
coefficient = coefficient
.checked_mul(10000)?
.checked_add(i128::from(digit))?;
}
if coefficient == 0 {
return Some(Decimal128::ZERO);
}
let mut exponent = (i32::from(weight) + 1 - i32::from(count)) * 4;
while coefficient % 10 == 0 {
coefficient /= 10;
exponent += 1;
}
if coefficient >= 10_i128.pow(34) {
return None;
}
native_parts(if sign == 0 { coefficient } else { -coefficient }, exponent)
}
fn postgres_encoded<const NATIVE: bool, const FIXED: bool>(
values: &[StoredDecimal],
) -> Result<Vec<u8>, Fault> {
let mut output = bytes::BytesMut::with_capacity(values.len() * 28);
for input in values {
if NATIVE {
if FIXED {
native_postgres(
native_scaled(input.native, 34, 4).unwrap(),
-4,
4,
&mut output,
);
} else {
native_postgres(
input.native.coefficient(),
input.native.exponent(),
(-input.native.exponent()).max(0) as u16,
&mut output,
);
}
} else {
postgres_codec::encode(input.value, FIXED, &mut output);
}
}
Ok(output.to_vec())
}
fn postgres_decoded<const NATIVE: bool>(values: &[StoredDecimal]) -> Result<Vec<[u8; 16]>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
native_postgres_decode(&input.postgres)
.unwrap()
.to_be_bytes()
} else {
postgres_codec::decode(&input.postgres).to_be_bytes()
}
})
.collect())
}
fn storage_cases() {
let inputs = [
"0",
"-0",
"1.2300",
"-9999.0001",
"999999999999999999999999999999.9999",
"-999999999999999999999999999999.9999",
]
.iter()
.cycle()
.take(120)
.map(|text| {
let value = Decimal::parse(*text).unwrap();
let native = native_parse(text).unwrap();
let mut postgres = bytes::BytesMut::new();
native_postgres(
native.coefficient(),
native.exponent(),
(-native.exponent()).max(0) as u16,
&mut postgres,
);
StoredDecimal {
value,
native,
key: format!(
"{:035}",
native_scaled(native, 34, 4).unwrap() + 10_i128.pow(34)
),
postgres: postgres.to_vec(),
}
})
.collect::<Vec<_>>();
compare(
"decimal_sqlite_encode",
&inputs,
1000,
sqlite_encoded::<false>,
sqlite_encoded::<true>,
|value| text_score(value),
);
compare(
"decimal_sqlite_decode",
&inputs,
1000,
sqlite_decoded::<false>,
sqlite_decoded::<true>,
|value| byte_score(value),
);
compare(
"decimal_postgres_encode",
&inputs,
1000,
postgres_encoded::<false, true>,
postgres_encoded::<true, true>,
|value| value.iter().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_postgres_native",
&inputs,
1000,
postgres_encoded::<false, false>,
postgres_encoded::<true, false>,
|value| value.iter().map(|value| u64::from(*value)).sum(),
);
compare(
"decimal_postgres_decode",
&inputs,
1000,
postgres_decoded::<false>,
postgres_decoded::<true>,
|value| value.iter().flatten().map(|value| u64::from(*value)).sum(),
);
}
+111
View File
@@ -0,0 +1,111 @@
// JSON 参照独立实现 Serde 的 Decimal 字符串合同,复用同一个 serde_json 解析器。
impl serde::Serialize for NativeDisplay {
fn serialize<Encoder: serde::Serializer>(
&self,
encoder: Encoder,
) -> Result<Encoder::Ok, Encoder::Error> {
encoder.collect_str(self)
}
}
impl<'input> serde::Deserialize<'input> for NativeDisplay {
fn deserialize<Decoder: serde::Deserializer<'input>>(
decoder: Decoder,
) -> Result<Self, Decoder::Error> {
struct Visitor;
impl<'input> serde::de::Visitor<'input> for Visitor {
type Value = NativeDisplay;
fn expecting(&self, output: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
output.write_str("an exact Decimal string")
}
fn visit_str<Error: serde::de::Error>(self, text: &str) -> Result<Self::Value, Error> {
native_parse(text)
.map(NativeDisplay)
.ok_or_else(|| Error::custom("invalid Decimal"))
}
}
decoder.deserialize_str(Visitor)
}
}
fn json_encoded<const NATIVE: bool>(values: &[DecimalInput]) -> Result<Vec<String>, Fault> {
Ok(values
.iter()
.map(|input| {
if NATIVE {
serde_json::to_string(&NativeDisplay(input.native)).unwrap()
} else {
serde_json::to_string(&input.value).unwrap()
}
})
.collect())
}
fn json_decoded<const NATIVE: bool>(values: &[String]) -> Result<Vec<Option<[u8; 16]>>, Fault> {
Ok(values
.iter()
.map(|text| {
if NATIVE {
serde_json::from_str::<NativeDisplay>(text)
.ok()
.map(|value| value.0.to_be_bytes())
} else {
serde_json::from_str::<Decimal>(text)
.ok()
.map(Decimal::to_be_bytes)
}
})
.collect())
}
fn wire_cases(inputs: &[DecimalInput]) {
let texts = inputs
.iter()
.map(|input| serde_json::to_string(&input.text).unwrap())
.collect::<Vec<_>>();
compare(
"decimal_json_encode",
inputs,
1000,
json_encoded::<false>,
json_encoded::<true>,
|value| text_score(value),
);
compare(
"decimal_json_decode",
&texts,
1000,
json_decoded::<false>,
json_decoded::<true>,
|value| byte_score(value),
);
let invalid = [
"1.25",
"null",
"[]",
"\"NaN\"",
"\"1e9999\"",
"\" 1\"",
"\"1\\u0000\"",
"\"1\" true",
]
.iter()
.cycle()
.take(112)
.map(|value| (*value).to_owned())
.collect::<Vec<_>>();
assert!(
json_decoded::<false>(&invalid)
.unwrap()
.iter()
.all(Option::is_none)
);
compare(
"decimal_json_invalid",
&invalid,
1000,
json_decoded::<false>,
json_decoded::<true>,
|value| byte_score(value),
);
}
+152
View File
@@ -0,0 +1,152 @@
fn fault() -> Fault {
&FaultInfo {
message: "integer arithmetic fault",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn map_reference(values: &[i64]) -> Result<Vec<i64>, Fault> {
let mut output = Vec::with_capacity(values.len());
for value in values {
output.push(value.checked_add(1).ok_or_else(fault)?);
}
Ok(output)
}
fn total_reference(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let adjusted = value.checked_add(1).ok_or_else(fault)?;
total = total.checked_add(adjusted).ok_or_else(fault)?;
}
Ok(total)
}
// 与跨包调用一致,投影失败和累计失败必须能定位到不同来源。
fn total_reference_distinct_faults(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let adjusted = value.checked_add(1).ok_or(&FaultInfo {
message: "integer arithmetic fault",
source: "reference/value.rs",
line: 1,
column: 1,
})?;
total = total.checked_add(adjusted).ok_or(&FaultInfo {
message: "integer arithmetic fault",
source: "reference/batch.rs",
line: 2,
column: 1,
})?;
}
Ok(total)
}
fn dispatch_generated(values: &[i64]) -> Result<i64, Fault> {
GENERATED_DISPATCH(values.first().copied().unwrap_or(0))
}
fn dispatch_reference(values: &[i64]) -> Result<i64, Fault> {
values
.first()
.copied()
.unwrap_or(0)
.checked_mul(2)
.ok_or_else(fault)
}
fn dispatch_total_reference(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let doubled = value.checked_mul(2).ok_or_else(fault)?;
total = total.checked_add(doubled).ok_or(&FaultInfo {
message: "integer overflow",
source: "reference/dispatch.rs",
line: 2,
column: 1,
})?;
}
Ok(total)
}
fn main() {
let values: Vec<i64> = (0..1024).map(|value| value % 17).collect();
for values in [&[i64::MAX][..], &[i64::MAX - 1, 1][..], &[i64::MIN][..]] {
assert_eq!(
GENERATED_MAP(values).map_err(|_| ()),
map_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_TOTAL(values).map_err(|_| ()),
total_reference(values).map_err(|_| ())
);
assert_eq!(
GENERATED_TOTAL(values).map_err(|fault| fault.message),
total_reference_distinct_faults(values).map_err(|fault| fault.message)
);
assert_eq!(
dispatch_generated(values).map_err(|_| ()),
dispatch_reference(values).map_err(|_| ())
);
}
let projection = GENERATED_TOTAL(&[i64::MAX]).unwrap_err();
let sum = GENERATED_TOTAL(&[i64::MAX - 1, 1]).unwrap_err();
assert_ne!(projection.source, sum.source);
assert_ne!(
total_reference_distinct_faults(&[i64::MAX])
.unwrap_err()
.source,
total_reference_distinct_faults(&[i64::MAX - 1, 1])
.unwrap_err()
.source
);
for values in [&[i64::MAX][..], &[i64::MAX / 2, 1][..], &[i64::MIN][..]] {
assert_eq!(
GENERATED_DISPATCH_TOTAL(values).map_err(|fault| fault.message),
dispatch_total_reference(values).map_err(|fault| fault.message)
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"package_map",
&values,
20000,
GENERATED_MAP,
map_reference,
|value| value.iter().map(|value| *value as u64).sum(),
);
compare(
"package_total",
&values,
20000,
GENERATED_TOTAL,
total_reference,
|value| *value as u64,
);
compare(
"package_total_distinct_faults",
&values,
20000,
GENERATED_TOTAL,
total_reference_distinct_faults,
|value| *value as u64,
);
compare(
"package_dispatch",
&[21],
3000000,
dispatch_generated,
dispatch_reference,
|value| *value as u64,
);
compare(
"package_dispatch_batch",
&values,
20000,
GENERATED_DISPATCH_TOTAL,
dispatch_total_reference,
|value| *value as u64,
);
}
+233
View File
@@ -0,0 +1,233 @@
// 隔离同一受检翻倍在内联表达式、Rust 函数边界和 Dever 生成路径上的差异。
// 共享故障元数据以排除错误对象大小和来源内容,原 packages 参照保持不变。
fn shared_fault() -> Fault {
GENERATED_DISPATCH(i64::MAX).unwrap_err()
}
fn dispatch_generated(values: &[i64]) -> Result<i64, Fault> {
GENERATED_DISPATCH(values.first().copied().unwrap_or(0))
}
fn adjust_generated(values: &[i64]) -> Result<i64, Fault> {
GENERATED_ADJUST(values.first().copied().unwrap_or(0))
}
fn inline_reference(values: &[i64]) -> Result<i64, Fault> {
values
.first()
.copied()
.unwrap_or(0)
.checked_mul(2)
.ok_or_else(shared_fault)
}
fn propagated_reference(values: &[i64]) -> Result<i64, Fault> {
let answer = values
.first()
.copied()
.unwrap_or(0)
.checked_mul(2)
.ok_or_else(shared_fault)?;
Ok(answer)
}
fn helper(value: i64) -> Result<i64, Fault> {
value.checked_mul(2).ok_or_else(shared_fault)
}
fn helper_reference(values: &[i64]) -> Result<i64, Fault> {
helper(values.first().copied().unwrap_or(0))
}
#[inline(always)]
fn early_helper(value: i64) -> Result<i64, Fault> {
value.checked_mul(2).ok_or_else(shared_fault)
}
fn early_helper_reference(values: &[i64]) -> Result<i64, Fault> {
early_helper(values.first().copied().unwrap_or(0))
}
fn independent_helper(value: i64) -> Result<i64, Fault> {
value.checked_mul(2).ok_or(&FaultInfo {
message: "integer arithmetic fault",
source: "reference.dever",
line: 1,
column: 1,
})
}
fn independent_helper_reference(values: &[i64]) -> Result<i64, Fault> {
independent_helper(values.first().copied().unwrap_or(0))
}
fn propagated_helper(value: i64) -> Result<i64, Fault> {
let answer = value.checked_mul(2).ok_or_else(shared_fault)?;
Ok(answer)
}
fn propagated_helper_reference(values: &[i64]) -> Result<i64, Fault> {
propagated_helper(values.first().copied().unwrap_or(0))
}
fn batch_generated(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
total = total
.checked_add(GENERATED_DISPATCH(*value)?)
.ok_or_else(shared_fault)?;
}
Ok(total)
}
fn batch_reference(values: &[i64]) -> Result<i64, Fault> {
let mut total = 0i64;
for value in values {
let doubled = value.checked_mul(2).ok_or_else(shared_fault)?;
total = total.checked_add(doubled).ok_or_else(shared_fault)?;
}
Ok(total)
}
fn main() {
let operations = [
dispatch_generated as fn(&[i64]) -> Result<i64, Fault>,
adjust_generated,
inline_reference,
propagated_reference,
helper_reference,
early_helper_reference,
propagated_helper_reference,
];
for value in [
i64::MIN,
i64::MIN / 2 - 1,
i64::MIN / 2,
-1,
0,
1,
21,
i64::MAX / 2,
i64::MAX / 2 + 1,
i64::MAX,
] {
for operation in operations {
assert_eq!(
operation(&[value]).map_err(|fault| fault.to_string()),
inline_reference(&[value]).map_err(|fault| fault.to_string())
);
}
assert_eq!(
independent_helper_reference(&[value]).map_err(|_| ()),
inline_reference(&[value]).map_err(|_| ())
);
}
for (name, operation) in [
(
"dispatch",
dispatch_generated as fn(&[i64]) -> Result<i64, Fault>,
),
("adjust", adjust_generated),
("propagated", propagated_reference),
("helper", helper_reference),
("early_helper", early_helper_reference),
("independent_helper", independent_helper_reference),
("propagated_helper", propagated_helper_reference),
] {
eprintln!(
"{name}_equals_dispatch={}",
std::ptr::fn_addr_eq(
operation,
dispatch_generated as fn(&[i64]) -> Result<i64, Fault>
)
);
}
let values = (0..256).map(|index| index * 17 - 2000).collect::<Vec<_>>();
for input in [
vec![],
vec![i64::MAX],
vec![i64::MIN],
vec![i64::MAX / 2, 1],
values.clone(),
] {
assert_eq!(
batch_generated(&input).map_err(|fault| fault.to_string()),
batch_reference(&input).map_err(|fault| fault.to_string())
);
}
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"scalar_matched_hot_path",
&[21],
3000000,
adjust_generated,
independent_helper_reference,
|value| *value as u64,
);
compare(
"scalar_inline",
&[21],
3000000,
dispatch_generated,
inline_reference,
|value| *value as u64,
);
compare_named(
"scalar_direct",
&[21],
3000000,
dispatch_generated,
adjust_generated,
|value| *value as u64,
"direct",
);
compare(
"scalar_propagated",
&[21],
3000000,
dispatch_generated,
propagated_reference,
|value| *value as u64,
);
compare(
"scalar_helper",
&[21],
3000000,
dispatch_generated,
helper_reference,
|value| *value as u64,
);
compare(
"scalar_early_helper",
&[21],
3000000,
dispatch_generated,
early_helper_reference,
|value| *value as u64,
);
compare(
"scalar_independent_helper",
&[21],
3000000,
dispatch_generated,
independent_helper_reference,
|value| *value as u64,
);
compare(
"scalar_propagated_helper",
&[21],
3000000,
dispatch_generated,
propagated_helper_reference,
|value| *value as u64,
);
compare(
"scalar_batch",
&values,
30000,
batch_generated,
batch_reference,
|value| *value as u64,
);
}
+71
View File
@@ -0,0 +1,71 @@
// 使用实际三条调用路径记录接受/拒绝,不把 Axum 默认行为误当成 Dever 合同。
fn query_checks() {
let suffix = "&count=-7&enabled=true&ratio=1.25";
let mut cases = vec![
("plain", format!("name=Alice{suffix}")),
("empty_text", format!("name={suffix}")),
("unicode", format!("name=%E9%9B%AA+test{suffix}")),
("escaped_separator", format!("name=a%26b%3Dc{suffix}")),
("escaped_percent", format!("name=%25{suffix}")),
(
"valid_replacement_character",
format!("name=%EF%BF%BD{suffix}"),
),
("duplicate_required", format!("name=a&name=b{suffix}")),
("duplicate_optional", format!("name=a{suffix}&tag=x&tag=y")),
("escaped_duplicate", format!("name=a&%6Eame=b{suffix}")),
("unknown", format!("name=a{suffix}&extra=x")),
("missing_required", "name=a&enabled=true&ratio=1.25".into()),
("optional_present", format!("name=a{suffix}&tag=x")),
("empty_pairs", format!("&&name=a&&{suffix}&")),
("missing_equals", format!("name{suffix}")),
("truncated_percent", format!("name=%{suffix}")),
("short_percent", format!("name=%A{suffix}")),
("invalid_hex", format!("name=%GG{suffix}")),
("invalid_utf8", format!("name=%FF{suffix}")),
("overlong_utf8", format!("name=%C0%AF{suffix}")),
("encoded_surrogate", format!("name=%ED%A0%80{suffix}")),
("nul", format!("name=%00{suffix}")),
];
for (name, value) in [
("int_min", "-9223372036854775808"),
("int_max", "9223372036854775807"),
("int_overflow", "9223372036854775808"),
("int_fraction", "1.5"),
("int_leading_zero", "007"),
("int_encoded_plus", "%2B7"),
] {
cases.push((
name,
format!("name=a&count={value}&enabled=true&ratio=1.25"),
));
}
for (name, value) in [
("float_nan", "NaN"),
("float_inf", "inf"),
("float_overflow", "1e999"),
("float_negative_zero", "-0"),
("float_exponent", "1.5e3"),
] {
cases.push((name, format!("name=a&count=7&enabled=true&ratio={value}")));
}
cases.push(("invalid_bool", "name=a&count=7&enabled=1&ratio=1.25".into()));
println!("case,dever,axum,serde_urlencoded,equal_values");
for (name, query) in cases {
let input = [query_input(&query)];
let actual = runtime_query::<QueryArguments>(&input);
let axum = axum_query::<QueryArguments>(&input);
let serde = serde_query::<QueryArguments>(&input);
let equal = match (&actual, &axum, &serde) {
(Ok(actual), Ok(axum), Ok(serde)) => actual == axum && actual == serde,
(Err(_), Err(_), Err(_)) => true,
_ => false,
};
println!(
"{name},{},{},{},{equal}",
actual.is_ok(),
axum.is_ok(),
serde.is_ok()
);
}
}
+167
View File
@@ -0,0 +1,167 @@
// 对比实际运行库、Axum Query 和其底层 serde_urlencoded;不包含网络或路由耗时。
// 默认库的错误输入行为另行检查,合法输入计时不能证明完整边界语义等价。
use axum::{extract::Query, http::Uri};
use dever_runtime::api::{self, Request};
use serde::{Deserialize, de::DeserializeOwned};
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct QueryArguments {
name: String,
count: i64,
enabled: bool,
ratio: f64,
tag: Option<String>,
}
#[derive(Debug, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
struct WideArguments {
field0: String,
field1: String,
field2: String,
field3: String,
field4: String,
field5: String,
field6: String,
field7: String,
field8: String,
field9: String,
field10: String,
field11: String,
field12: String,
field13: String,
field14: String,
field15: String,
field16: String,
field17: String,
field18: String,
field19: String,
field20: String,
field21: String,
field22: String,
field23: String,
field24: String,
field25: String,
field26: String,
field27: String,
field28: String,
field29: String,
field30: String,
field31: String,
}
struct QueryInput {
request: Request,
uri: Uri,
}
fn query_fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo {
message: "invalid query",
source: "query-comparison",
line: 1,
column: 1,
}
}
fn runtime_query<Arguments: DeserializeOwned>(
values: &[QueryInput],
) -> Result<Option<Arguments>, Fault> {
let Some(input) = values.first() else {
return Ok(None);
};
api::input(&input.request)
.map_err(query_fault)?
.decode(dever_runtime::wire::decode)
.map(Some)
.map_err(query_fault)
}
fn axum_query<Arguments: DeserializeOwned>(
values: &[QueryInput],
) -> Result<Option<Arguments>, Fault> {
let Some(input) = values.first() else {
return Ok(None);
};
Query::<Arguments>::try_from_uri(&input.uri)
.map(|Query(arguments)| Some(arguments))
.map_err(query_fault)
}
fn serde_query<Arguments: DeserializeOwned>(
values: &[QueryInput],
) -> Result<Option<Arguments>, Fault> {
let Some(input) = values.first() else {
return Ok(None);
};
serde_urlencoded::from_str(input.uri.query().unwrap_or_default())
.map(Some)
.map_err(query_fault)
}
fn query_input(query: &str) -> QueryInput {
let target = format!("/test?{query}");
QueryInput {
uri: target.parse().unwrap(),
request: axum::http::Request::builder()
.uri(target)
.body(Default::default())
.unwrap(),
}
}
fn query_comparison<Arguments: DeserializeOwned + PartialEq + std::fmt::Debug>(
name: &str,
query: &str,
count: usize,
) {
let input = [query_input(query)];
for (label, decoder) in [
("axum", axum_query::<Arguments> as fn(&[QueryInput]) -> _),
(
"serde_urlencoded",
serde_query::<Arguments> as fn(&[QueryInput]) -> _,
),
] {
compare_named(
&format!("query_{name}_{label}"),
&input,
count,
runtime_query::<Arguments>,
decoder,
|output| u64::from(output.is_some()),
label,
);
}
}
fn main() {
if std::env::args().any(|argument| argument == "--check") {
query_checks();
return;
}
println!("case,sample,implementation,elapsed_ns,checksum");
for (name, value, count) in [
("plain", "Alice".to_owned(), 20_000),
("unicode", "%E9%9B%AA+test".to_owned(), 20_000),
("escaped", "%22%5C%00%26%3D%25".repeat(8), 10_000),
("long", "value%2B%E9%9B%AA".repeat(256), 1_000),
] {
query_comparison::<QueryArguments>(
name,
&format!("name={value}&count=-7&enabled=true&ratio=1.25&tag=demo"),
count,
);
}
for (name, value, count) in [
("wide_plain", "testvalue".to_owned(), 2_000),
("wide_escaped", "value%2B%E9%9B%AA".repeat(8), 500),
] {
let query = (0..32)
.map(|index| format!("field{index}={value}"))
.collect::<Vec<_>>()
.join("&");
query_comparison::<WideArguments>(name, &query, count);
}
}
+118
View File
@@ -0,0 +1,118 @@
fn integer_reference(value: i64) -> Result<i64, Fault> {
let failure = &FaultInfo {
source: "reference",
line: 1,
column: 1,
message: "integer arithmetic fault",
};
value
.checked_mul(3)
.ok_or(failure)?
.checked_add(7)
.ok_or(failure)?
.checked_div(2)
.ok_or(failure)
}
fn list_reference(values: &[i64]) -> Result<i64, Fault> {
let failure = &FaultInfo {
source: "reference",
line: 1,
column: 1,
message: "integer overflow",
};
values
.iter()
.try_fold(0i64, |sum, value| sum.checked_add(*value))
.ok_or(failure)
}
fn text_reference(first: &str, second: &str) -> Result<String, Fault> {
let mut result = String::with_capacity(first.len() + second.len());
result.push_str(first);
result.push_str(second);
Ok(result)
}
fn sample(
name: &str,
sample: usize,
implementation: &str,
count: usize,
mut operation: impl FnMut(usize) -> usize,
) {
let start = Instant::now();
let mut checksum = 0usize;
for position in 0..count {
checksum = checksum.wrapping_add(black_box(operation(black_box(position))));
}
println!(
"{name},{sample},{implementation},{},{checksum}",
start.elapsed().as_nanos()
);
}
fn main() {
let values: Vec<i64> = (0..1024)
.map(|position| (position * 37 % 100) as i64)
.collect();
let text = "雪山远处有树林,近处有人撑伞。".repeat(4);
for value in [i64::MIN, -123, -1, 0, 100, i64::MAX] {
assert_eq!(
GENERATED_INTEGER(value).map_err(|_| ()),
integer_reference(value).map_err(|_| ())
);
}
assert_eq!(
GENERATED_LIST(&values).unwrap(),
list_reference(&values).unwrap()
);
assert!(GENERATED_LIST(&[i64::MAX, 1]).is_err());
assert!(list_reference(&[i64::MAX, 1]).is_err());
assert_eq!(
GENERATED_TEXT(&text, &text).unwrap(),
text_reference(&text, &text).unwrap()
);
println!("case,sample,implementation,elapsed_ns,checksum");
// 求和两侧共用计时函数,保留同函数 control,避免把调用端布局计入生成器差距。
compare(
"list_sum_1024",
&values,
40_000,
GENERATED_LIST,
list_reference,
|value| *value as u64,
);
for iteration in 0..9 {
for generated in if iteration % 2 == 0 {
[true, false]
} else {
[false, true]
} {
let implementation = if generated { "dever" } else { "rust" };
if generated {
sample(
"checked_int",
iteration,
implementation,
2_000_000,
|position| GENERATED_INTEGER(position as i64).unwrap() as usize,
);
sample("text_join", iteration, implementation, 300_000, |_| {
black_box(GENERATED_TEXT(black_box(&text), black_box(&text)).unwrap()).len()
});
} else {
sample(
"checked_int",
iteration,
implementation,
2_000_000,
|position| integer_reference(position as i64).unwrap() as usize,
);
sample("text_join", iteration, implementation, 300_000, |_| {
black_box(text_reference(black_box(&text), black_box(&text)).unwrap()).len()
});
}
}
}
}
+204
View File
@@ -0,0 +1,204 @@
// 文件创建和清理位于计时之外;同一文件先预热,不把页缓存当作编译器收益。
struct ResourceCase {
path: String,
limit: i64,
}
struct ResourceDirectory(std::path::PathBuf);
impl Drop for ResourceDirectory {
fn drop(&mut self) {
fs::remove_dir_all(&self.0).unwrap();
}
}
fn resource_fault(_: impl std::fmt::Display) -> Fault {
&FaultInfo {
source: "resource-reference",
line: 1,
column: 1,
message: "resource failure",
}
}
fn generated_open(values: &[ResourceCase]) -> Result<bool, Fault> {
values.first().map_or(Ok(true), |value| {
GENERATED_OPEN(&value.path).map_err(resource_fault)
})
}
fn reference_open(values: &[ResourceCase]) -> Result<bool, Fault> {
let Some(value) = values.first() else {
return Ok(true);
};
let file = ReferenceFile::open(&value.path).map_err(resource_fault)?;
let alias = file.clone();
alias.close().map_err(resource_fault)?;
Ok(true)
}
fn generated_read(values: &[ResourceCase]) -> Result<Vec<u8>, Fault> {
values.first().map_or(Ok(Vec::new()), |value| {
GENERATED_READ(&value.path, value.limit).map_err(resource_fault)
})
}
fn reference_read(values: &[ResourceCase]) -> Result<Vec<u8>, Fault> {
let Some(value) = values.first() else {
return Ok(Vec::new());
};
let file = ReferenceFile::open(&value.path).map_err(resource_fault)?;
let bytes = file
.read(value.limit)
.map_err(resource_fault)?
.unwrap_or_default();
file.close().map_err(resource_fault)?;
Ok(bytes)
}
fn generated_stream(values: &[ResourceCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
GENERATED_STREAM(&value.path, value.limit).map_err(resource_fault)
})
}
fn reference_stream(values: &[ResourceCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
reference_consume(&value.path, value.limit, false).map_err(resource_fault)
})
}
fn generated_partial(values: &[ResourceCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
GENERATED_PARTIAL(&value.path, value.limit).map_err(resource_fault)
})
}
fn reference_partial(values: &[ResourceCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
reference_consume(&value.path, value.limit, true).map_err(resource_fault)
})
}
fn generated_closed(values: &[ResourceCase]) -> Result<bool, Fault> {
Ok(values
.first()
.is_none_or(|value| GENERATED_CLOSED(&value.path).is_err()))
}
fn reference_closed(values: &[ResourceCase]) -> Result<bool, Fault> {
let Some(value) = values.first() else {
return Ok(true);
};
let file = ReferenceFile::open(&value.path).map_err(resource_fault)?;
let stream = ReferenceStream::new(&file, 64).map_err(resource_fault)?;
file.close().map_err(resource_fault)?;
Ok(stream.next().unwrap().is_err())
}
fn main() {
let path =
std::env::temp_dir().join(format!("dever-resource-performance-{}", std::process::id()));
fs::create_dir(&path).unwrap();
let directory = ResourceDirectory(path);
let path = directory.0.join("input");
let bytes = (0..65537)
.map(|index| (index % 251) as u8)
.collect::<Vec<_>>();
fs::write(&path, &bytes).unwrap();
let path = path.to_str().unwrap().to_owned();
assert_eq!(GENERATED_READ(&path, 1_000_000).unwrap(), bytes);
for limit in [0, -1, i64::MAX] {
assert!(GENERATED_READ(&path, limit).is_err());
assert!(ReferenceFile::open(&path).unwrap().read(limit).is_err());
}
let expected: i64 = bytes.iter().map(|byte| i64::from(*byte)).sum();
for limit in [64, 4096] {
assert_eq!(GENERATED_STREAM(&path, limit).unwrap(), expected);
assert_eq!(GENERATED_PARTIAL(&path, limit).unwrap(), expected);
}
let closed = GENERATED_CLOSED(&path).unwrap_err();
assert!(
closed.to_string().contains("ReadEvent.Failed")
&& format!("{closed:?}").contains("file is closed"),
"{closed}"
);
let file = ReferenceFile::open(&path).unwrap();
let alias = file.clone();
assert_eq!(file.read(2).unwrap().unwrap(), bytes[..2]);
assert_eq!(alias.read(2).unwrap().unwrap(), bytes[2..4]);
let stream = ReferenceStream::new(&file, 2).unwrap();
alias.close().unwrap();
assert!(stream.next().unwrap().is_err());
assert!(stream.next().is_none());
println!("case,sample,implementation,elapsed_ns,checksum");
let values = [ResourceCase {
path: path.clone(),
limit: 64,
}];
compare(
"file_open_close",
&values,
2000,
generated_open,
reference_open,
|value| u64::from(*value),
);
compare(
"file_read_64",
&values,
2000,
generated_read,
reference_read,
|value| value.len() as u64,
);
compare(
"stream_read_64",
&values,
50,
generated_stream,
reference_stream,
|value| *value as u64,
);
compare(
"stream_resume_64",
&values,
50,
generated_partial,
reference_partial,
|value| *value as u64,
);
compare(
"stream_closed",
&values,
2000,
generated_closed,
reference_closed,
|value| u64::from(*value),
);
let values = [ResourceCase { path, limit: 4096 }];
compare(
"file_read_4096",
&values,
1000,
generated_read,
reference_read,
|value| value.len() as u64,
);
compare(
"stream_read_4096",
&values,
200,
generated_stream,
reference_stream,
|value| *value as u64,
);
compare(
"stream_resume_4096",
&values,
200,
generated_partial,
reference_partial,
|value| *value as u64,
);
}
+111
View File
@@ -0,0 +1,111 @@
// 独立 Rust 参照保留文件的跨线程别名及同步流的共享游标,不调用 Dever I/O。
use std::{
cell::RefCell,
fs,
io::Read,
rc::Rc,
sync::{Arc, Mutex},
};
#[derive(Clone)]
struct ReferenceFile(Arc<Mutex<Option<fs::File>>>);
impl ReferenceFile {
fn open(path: &str) -> Result<Self, String> {
fs::File::open(path)
.map(|file| Self(Arc::new(Mutex::new(Some(file)))))
.map_err(|error| error.to_string())
}
fn read(&self, limit: i64) -> Result<Option<Vec<u8>>, String> {
let limit = usize::try_from(limit)
.ok()
.filter(|limit| *limit > 0)
.ok_or("read limit must be positive")?;
let mut guard = self.0.lock().map_err(|_| "file lock poisoned")?;
let file = guard.as_mut().ok_or("file is closed")?;
let mut bytes = Vec::new();
bytes
.try_reserve_exact(limit)
.map_err(|error| error.to_string())?;
bytes.resize(limit, 0);
let count = file.read(&mut bytes).map_err(|error| error.to_string())?;
bytes.truncate(count);
Ok((count != 0).then_some(bytes))
}
fn close(&self) -> Result<(), String> {
drop(
self.0
.lock()
.map_err(|_| "file lock poisoned")?
.take()
.ok_or("file is closed")?,
);
Ok(())
}
}
#[derive(Clone)]
struct ReferenceStream(Rc<RefCell<Option<(ReferenceFile, i64)>>>);
impl ReferenceStream {
fn new(file: &ReferenceFile, limit: i64) -> Result<Self, String> {
usize::try_from(limit)
.ok()
.filter(|limit| *limit > 0)
.ok_or("read limit must be positive")?;
if file.0.lock().map_err(|_| "file lock poisoned")?.is_none() {
return Err("file is closed".into());
}
Ok(Self(Rc::new(RefCell::new(Some((file.clone(), limit))))))
}
fn next(&self) -> Option<Result<Vec<u8>, String>> {
let mut source = self.0.borrow_mut();
let (file, limit) = source.as_ref()?;
match file.read(*limit) {
Ok(Some(bytes)) => Some(Ok(bytes)),
Ok(None) => {
*source = None;
None
}
Err(error) => {
*source = None;
Some(Err(error))
}
}
}
fn close(&self) {
*self.0.borrow_mut() = None;
}
}
fn reference_count(bytes: &[u8], total: i64) -> Result<i64, String> {
bytes.iter().try_fold(total, |sum, byte| {
sum.checked_add(i64::from(*byte))
.ok_or_else(|| "integer overflow".into())
})
}
fn reference_consume(path: &str, limit: i64, partial: bool) -> Result<i64, String> {
let file = ReferenceFile::open(path)?;
let stream = ReferenceStream::new(&file, limit)?;
let alias = partial.then(|| stream.clone());
let mut total = 0;
if partial {
if let Some(bytes) = stream.next() {
total = reference_count(&bytes?, total)?;
}
}
while let Some(bytes) = alias.as_ref().unwrap_or(&stream).next() {
total = reference_count(&bytes?, total)?;
}
if partial {
stream.close();
alias.as_ref().unwrap().close();
}
file.close()?;
Ok(total)
}
+56
View File
@@ -0,0 +1,56 @@
use std::hint::black_box;
use std::time::Instant;
#[inline(never)]
fn measure<const BORROW: bool, Input: ?Sized, Output: std::fmt::Debug>(operation: fn(&Input) -> Result<Output, Fault>, values: &Input, count: usize, score: fn(&Output) -> u64) -> (u128, u64) {
let operation = black_box(operation);
let start = Instant::now();
let mut checksum = 0u64;
for _ in 0..count {
if BORROW {
// 保留 Result,通过借用观察输出,隔离取出所有权时的元数据搬运。
// 屏障只接收计分值;传入内部引用会使计时环重读 Result 标签并额外访存。
let value = operation(black_box(values));
checksum = checksum.wrapping_add(black_box(score(value.as_ref().unwrap())));
} else {
let value = black_box(operation(black_box(values)).unwrap());
checksum = checksum.wrapping_add(score(&value));
}
}
(start.elapsed().as_nanos(), checksum)
}
fn compare<Input, Output: PartialEq + std::fmt::Debug>(name: &str, values: &[Input], count: usize, generated: fn(&[Input]) -> Result<Output, Fault>, reference: fn(&[Input]) -> Result<Output, Fault>, score: fn(&Output) -> u64) {
compare_named(name, values, count, generated, reference, score, "rust");
}
fn compare_named<Input, Output: PartialEq + std::fmt::Debug>(name: &str, values: &[Input], count: usize, generated: fn(&[Input]) -> Result<Output, Fault>, reference: fn(&[Input]) -> Result<Output, Fault>, score: fn(&Output) -> u64, reference_name: &str) {
compare_output::<false, _, _>(name, values, &[], count, generated, reference, score, reference_name);
}
fn compare_borrowed<Input, Output: PartialEq + std::fmt::Debug>(name: &str, values: &[Input], count: usize, generated: fn(&[Input]) -> Result<Output, Fault>, reference: fn(&[Input]) -> Result<Output, Fault>, score: fn(&Output) -> u64) {
compare_output::<true, _, _>(&format!("{name}_borrowed"), values, &[], count, generated, reference, score, "rust");
}
fn compare_bytes<Output: PartialEq + std::fmt::Debug>(name: &str, values: &dever_runtime::Bytes, count: usize, generated: fn(&dever_runtime::Bytes) -> Result<Output, Fault>, reference: fn(&dever_runtime::Bytes) -> Result<Output, Fault>, score: fn(&Output) -> u64) {
compare_output::<false, _, _>(name, values, &dever_runtime::Bytes::new(), count, generated, reference, score, "rust");
}
fn compare_output<const BORROW: bool, Input: ?Sized, Output: PartialEq + std::fmt::Debug>(name: &str, values: &Input, empty: &Input, count: usize, generated: fn(&Input) -> Result<Output, Fault>, reference: fn(&Input) -> Result<Output, Fault>, score: fn(&Output) -> u64, reference_name: &str) {
assert_eq!(generated(values).unwrap(), reference(values).unwrap());
assert_eq!(generated(empty).unwrap(), reference(empty).unwrap());
for operation in [generated, reference] { black_box(measure::<BORROW, _, _>(operation, values, 2000, score)); }
for sample in 0..15 {
// 每个标签各占首、中、末位五次,并交替正反顺序,避免固定前后关系。
let mut order = [("dever", generated), (reference_name, reference), ("control", generated)];
order.rotate_left(sample % 3);
if sample / 3 % 2 == 1 { order.swap(0, 1); }
let mut expected = None;
for (implementation, operation) in order {
let (elapsed, checksum) = measure::<BORROW, _, _>(operation, values, count, score);
if let Some(expected) = expected { assert_eq!(checksum, expected); }
expected = Some(checksum);
println!("{name},{sample},{implementation},{elapsed},{checksum}");
}
}
}
+323
View File
@@ -0,0 +1,323 @@
use indexmap::IndexMap;
struct Lookup {
generated: Map<i64, i64>,
reference: IndexMap<i64, i64>,
keys: Vec<i64>,
}
struct TextLookup {
generated: Map<String, i64>,
reference: IndexMap<String, i64>,
keys: Vec<String>,
}
fn text_lookup_generated(values: &[TextLookup]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| {
GENERATED_TEXT_LOOKUP(&value.generated, &value.keys)
})
}
fn text_lookup_reference(values: &[TextLookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut total = 0i64;
for key in &value.keys {
total = total
.checked_add(value.reference.get(key.as_str()).copied().unwrap_or(0))
.ok_or_else(overflow)?;
}
Ok(total)
}
fn text_lookup_same_table(values: &[TextLookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut total = 0i64;
for key in &value.keys {
total = total
.checked_add(value.generated.get(key.as_str()).copied().unwrap_or(0))
.ok_or_else(overflow)?;
}
Ok(total)
}
fn text_lookup_comparisons() {
let reference_first = match std::env::var("DEVER_BENCH_MAP_ORDER")
.as_deref()
.unwrap_or("dever-first")
{
"dever-first" => false,
"rust-first" => true,
order => panic!("unknown table construction order: {order}"),
};
for count in [512, 1024, 2048, 4096] {
let keys: Vec<_> = (0..count)
.map(|index| format!("lookup-key-{index}"))
.collect();
let (mut generated, mut reference) = if reference_first {
let reference = IndexMap::with_capacity(count);
(Map::with_capacity(count), reference)
} else {
(Map::with_capacity(count), IndexMap::with_capacity(count))
};
for key in &keys {
if reference_first {
reference.insert(key.clone(), 120);
generated.insert(key.clone(), 120);
} else {
generated.insert(key.clone(), 120);
reference.insert(key.clone(), 120);
}
}
assert_eq!(
GENERATED_TEXT_LOOKUP(&generated, &["missing".to_owned()]).unwrap(),
0
);
let values = [TextLookup {
generated,
reference,
keys,
}];
compare(
&format!("map_text_lookup_{count}"),
&values,
200,
text_lookup_generated,
text_lookup_reference,
|value| *value as u64,
);
// 同表只隔离生成层;独立 IndexMap 参照不能被这个控制组替代。
compare_named(
&format!("map_text_lookup_{count}_same_table"),
&values,
200,
text_lookup_generated,
text_lookup_same_table,
|value| *value as u64,
"rust_runtime",
);
}
}
fn overflow() -> Fault {
&FaultInfo {
message: "integer overflow",
source: "reference.dever",
line: 1,
column: 1,
}
}
fn lookup_generated(values: &[Lookup]) -> Result<i64, Fault> {
match values.first() {
Some(value) => GENERATED_LOOKUP(&value.generated, &value.keys),
None => Ok(0),
}
}
fn lookup_reference(values: &[Lookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut answer = 0i64;
for key in &value.keys {
answer = answer
.checked_add(value.reference.get(key).copied().unwrap_or(0))
.ok_or_else(overflow)?;
}
Ok(answer)
}
fn lookup_same_table(values: &[Lookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
let mut answer = 0i64;
for key in &value.keys {
answer = answer
.checked_add(value.generated.get(key).copied().unwrap_or(0))
.ok_or_else(overflow)?;
}
Ok(answer)
}
fn scan_generated(values: &[Lookup]) -> Result<i64, Fault> {
match values.first() {
Some(value) => GENERATED_SCAN(&value.generated),
None => Ok(0),
}
}
fn scan_reference(values: &[Lookup]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.reference.values().try_fold(0i64, |total, item| {
total.checked_add(*item).ok_or_else(overflow)
})
}
fn map_generated(values: &[i64]) -> Result<Vec<(i64, i64)>, Fault> {
GENERATED_ENTRIES(values)
}
fn map_reference(values: &[i64]) -> Result<Vec<(i64, i64)>, Fault> {
let mut result = IndexMap::new();
for value in values {
result.insert(*value, *value);
}
Ok(result.into_iter().collect())
}
fn list_reference(values: &[i64]) -> Result<Vec<i64>, Fault> {
let mut result = Vec::new();
for value in values {
result.push(*value);
}
Ok(result)
}
fn buffer_reference(values: &dever_runtime::Bytes) -> Result<BUFFER_STATE, Fault> {
let mut items = Vec::new();
let mut count = 0i64;
for value in values.iter() {
items.push(i64::from(*value));
count = count.checked_add(1).ok_or_else(overflow)?;
}
Ok(BUFFER_STATE {
field0: items,
field1: count,
})
}
fn buffer_inspect_reference(values: &dever_runtime::Bytes) -> Result<BUFFER_STATE, Fault> {
let mut items = Vec::new();
let mut count = 0i64;
for value in values.iter() {
count = (items.len() as i64).checked_add(1).ok_or_else(overflow)?;
items.push(i64::from(*value));
}
Ok(BUFFER_STATE {
field0: items,
field1: count,
})
}
fn read_records_reference(values: &[SCALAR_ROW]) -> Result<i64, Fault> {
let mut total = 0i64;
for row in values {
let projected = row
.field0
.checked_add(row.field1)
.ok_or_else(overflow)?
.checked_add(row.field2)
.ok_or_else(overflow)?;
total = total.checked_add(projected).ok_or_else(overflow)?;
}
Ok(total)
}
fn main() {
let values: Vec<i64> = (0..1024).collect();
let lookup = Lookup {
generated: GENERATED_BUILD_MAP(&values).unwrap(),
reference: values.iter().map(|value| (*value, *value)).collect(),
keys: (0..1024).map(|index| (index * 17) % 1152).collect(),
};
let lookups = [lookup];
let overflow_case = [Lookup {
generated: GENERATED_BUILD_MAP(&[i64::MAX, 1]).unwrap(),
reference: [(i64::MAX, i64::MAX), (1, 1)].into_iter().collect(),
keys: vec![i64::MAX, 1],
}];
assert!(scan_generated(&overflow_case).is_err());
assert!(scan_reference(&overflow_case).is_err());
assert!(lookup_generated(&overflow_case).is_err());
assert!(lookup_reference(&overflow_case).is_err());
assert_eq!(
map_generated(&[3, 1, 3, 2]).unwrap(),
map_reference(&[3, 1, 3, 2]).unwrap()
);
println!("case,sample,implementation,elapsed_ns,checksum");
text_lookup_comparisons();
for count in [512, 1024, 2048, 4096] {
let bytes = dever_runtime::Bytes::from(vec![120u8; count]);
let rows: Vec<_> = bytes
.iter()
.map(|value| SCALAR_ROW {
field0: i64::from(*value),
field1: 0,
field2: 0,
})
.collect();
compare(
&format!("list_record_read_{count}"),
&rows,
1000,
GENERATED_RECORD_READ,
read_records_reference,
|value| *value as u64,
);
compare_bytes(
&format!("record_update_{count}"),
&bytes,
1000,
GENERATED_BUFFER_BUILD,
buffer_reference,
|value| value.field1 as u64,
);
compare_bytes(
&format!("record_inspect_{count}"),
&bytes,
1000,
GENERATED_BUFFER_INSPECT,
buffer_inspect_reference,
|value| value.field1 as u64,
);
}
compare(
"map_lookup",
&lookups,
2000,
lookup_generated,
lookup_reference,
|value| *value as u64,
);
// 同一张表隔离随机哈希布局;独立 IndexMap 参照仍保留。
compare_named(
"map_lookup_same_table",
&lookups,
2000,
lookup_generated,
lookup_same_table,
|value| *value as u64,
"rust_runtime",
);
compare(
"map_scan",
&lookups,
20000,
scan_generated,
scan_reference,
|value| *value as u64,
);
compare(
"map_build",
&values[..512],
400,
map_generated,
map_reference,
|value| value.len() as u64,
);
compare(
"list_build",
&values,
1000,
GENERATED_BUILD_LIST,
list_reference,
|value| value.len() as u64,
);
}
+102
View File
@@ -0,0 +1,102 @@
// 独立使用 Tokio 的取消与排空参照;不调用 Dever 的 Scope、Task 或生成函数。
use tokio_util::task::{AbortOnDropHandle, task_tracker::TaskTrackerToken};
impl NativeTasks {
fn tracked_leaf<Value, Work>(
&self,
tasks: &TaskTracker,
call: impl FnOnce() -> Work + Send + 'static,
) -> Result<AbortOnDropHandle<(Value, TaskTrackerToken)>, Fault>
where
Value: Send + 'static,
Work: std::future::Future<Output = Value> + Send + 'static,
{
if tasks.is_closed() {
return Err(task_fault("closed task scope"));
}
let permit = self.tasks.clone().try_acquire_owned().map_err(task_fault)?;
let tracking = tasks.token();
let future = async move {
let result = call().await;
drop(permit);
result
};
// Future 先释放;未领取的输出也必须先于跟踪令牌释放。
Ok(AbortOnDropHandle::new(tokio::spawn(
future.map(move |result| (result, tracking)),
)))
}
}
fn reference_cancel_scoped(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.worker_scope(async |tasks| {
let task = value.native.tracked_leaf(tasks, || native_fallible_sleep(60000))?;
task.abort();
match task.await {
Err(error) if error.is_cancelled() => (),
Err(error) => return Err(NativeAsyncFailure::Fault(task_fault(error))),
Ok((result, _tracking)) => result?,
}
Ok::<_, NativeAsyncFailure>(1)
})?.map_err(Into::into)
}
fn cancel_scoped(cases: &[TaskCase]) {
compare_named("task_cancel_scoped", cases, 1000,
generated_cancel, reference_cancel_scoped, |value| *value as u64, "rust_scoped");
}
fn check_cancel_cleanup() {
let native = NativeTasks::new();
native.worker_scope(async |tasks| {
let task = native.tracked_leaf(tasks, || async { 42 }).unwrap();
let (value, tracking) = task.await.unwrap();
assert_eq!(value, 42);
drop(tracking);
let permits = native.tasks.clone().acquire_many_owned(8).await.unwrap();
assert!(native.tracked_leaf(tasks, || async {}).is_err());
drop(permits);
tasks.close();
assert!(native.tracked_leaf(tasks, || async {}).is_err());
}).unwrap();
// 立即放弃、已启动 Future、未领取输出分别验证,根 panic 也必须排空。
for stage in 0..3 {
for root_panics in [false, true] {
let dropped = Arc::new(AtomicBool::new(false));
let observed = dropped.clone();
let owned = WorkerOutput(dropped);
let outcome = native.worker_scope(async |tasks| {
let (started, ready) = tokio::sync::oneshot::channel();
let task = native.tracked_leaf(tasks, move || async move {
if stage != 0 { started.send(()).unwrap(); }
if stage != 2 { std::future::pending::<()>().await; }
owned
}).unwrap();
if stage != 0 { ready.await.unwrap(); }
if stage == 2 {
while !task.is_finished() { tokio::task::yield_now().await; }
assert!(!observed.load(Ordering::SeqCst));
}
if root_panics { panic!("root failure after leaf submission"); }
drop(task);
});
assert_eq!(outcome.is_err(), root_panics);
assert!(observed.load(Ordering::SeqCst));
assert_eq!(native.tasks.available_permits(), 8);
}
}
native.worker_scope(async |tasks| {
let task = native.tracked_leaf(tasks, || async { Err::<(), _>("business failure") }).unwrap();
while !task.is_finished() { tokio::task::yield_now().await; }
task.abort();
let (result, tracking) = task.await.unwrap();
assert_eq!(result, Err("business failure"));
drop(tracking);
let task = native.tracked_leaf(tasks, || async { panic!("leaf failure") }).unwrap();
assert!(task.await.unwrap_err().is_panic());
}).unwrap();
println!("leaf cleanup: capacity, closed scope, inputs, future, unclaimed output, root panic, business error, task panic and re-entry passed");
}
+76
View File
@@ -0,0 +1,76 @@
// 只由显式检查入口运行;同步通道控制 worker 时点,不用睡眠猜测退出顺序。
use std::sync::atomic::{AtomicBool, Ordering};
struct WorkerOutput(Arc<AtomicBool>);
impl Drop for WorkerOutput {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
fn check_worker_cleanup() {
assert!(matches!(native_blocking_sleep(-1), Err(NativeAsyncFailure::Sleep(message, _))
if message == "sleep duration must be nonnegative"));
let native = NativeTasks::new();
native.worker_scope(async |tasks| {
assert_eq!(native.tracked_blocking(tasks, || 42).await.unwrap(), 42);
let failure = native.tracked_blocking(tasks, || Err::<(), _>("business failure")).await;
assert_eq!(failure.unwrap(), Err("business failure"));
tasks.close();
assert!(native.tracked_blocking(tasks, || panic!("closed scope ran work")).await.is_err());
}).unwrap();
let called = Arc::new(AtomicBool::new(false));
native.worker_scope(async |tasks| {
let permits = native.workers.clone().acquire_many_owned(2).await.unwrap();
let observed = called.clone();
let queued = native.tracked_blocking(tasks, move || observed.store(true, Ordering::SeqCst));
let mut queued = Box::pin(queued);
assert!(futures_util::poll!(queued.as_mut()).is_pending());
drop(queued);
drop(permits);
}).unwrap();
assert!(!called.load(Ordering::SeqCst));
// 调用者取消、worker panic、根入口 panic 都必须排空真实工作和未领取结果。
for worker_panics in [false, true] {
for root_panics in [false, true] {
let dropped = Arc::new(AtomicBool::new(false));
let observed = dropped.clone();
let (release, blocked) = std::sync::mpsc::channel();
let (started, ready) = tokio::sync::oneshot::channel();
let worker = move || {
started.send(()).unwrap();
blocked.recv_timeout(Duration::from_secs(2)).unwrap();
let output = WorkerOutput(dropped);
if worker_panics { std::panic::panic_any(output); }
output
};
let result = native.worker_scope(async |tasks| {
let mut pending = Box::pin(native.tracked_blocking(tasks, worker));
assert!(futures_util::poll!(pending.as_mut()).is_pending());
ready.await.unwrap();
drop(pending);
assert_eq!(native.workers.available_permits(), 1);
assert!(!tasks.is_empty());
assert!(!observed.load(Ordering::SeqCst));
release.send(()).unwrap();
if root_panics { panic!("root failure after worker submission"); }
});
assert_eq!(result.is_err(), root_panics);
assert!(observed.load(Ordering::SeqCst));
assert_eq!(native.workers.available_permits(), 2);
}
}
let claimed = native.worker_scope(async |tasks| {
let result = native.tracked_blocking(tasks, || String::from("owned output")).await.unwrap();
tasks.close();
tasks.wait().await;
result
}).unwrap();
assert_eq!(claimed, "owned output");
assert_eq!(native.worker_scope(async |tasks| native.tracked_blocking(tasks, || 7).await).unwrap().unwrap(), 7);
println!("worker cleanup: success, business error, closed scope, queued cancellation, running cancellation, panic, result drop and re-entry passed");
}
+404
View File
@@ -0,0 +1,404 @@
// 保留原单次入口参照,另外区分入口成本和同一入口内连续执行的任务成本。
// 每个 steady 样本仍逐次创建、等待并清理真实任务,只复用父入口。
const CONTINUOUS_CALLS: usize = 16;
// 与 Dever async_total 保持同样的可失败计时分支和携带文本的错误合同,不加填充字段。
enum NativeAsyncFailure {
Fault(Fault),
Sleep(String, Fault),
}
impl From<Fault> for NativeAsyncFailure {
fn from(value: Fault) -> Self {
Self::Fault(value)
}
}
impl From<NativeAsyncFailure> for Fault {
fn from(value: NativeAsyncFailure) -> Self {
match value {
NativeAsyncFailure::Fault(fault) => fault,
NativeAsyncFailure::Sleep(message, fault) => {
drop(message);
fault
}
}
}
}
async fn native_fallible_sleep(millis: i64) -> Result<(), NativeAsyncFailure> {
let fail = |message: &str| NativeAsyncFailure::Sleep(message.to_owned(), task_fault("timer"));
let millis = u64::try_from(millis).map_err(|_| fail("sleep duration must be nonnegative"))?;
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(millis))
.ok_or_else(|| fail("sleep deadline is out of range"))?;
tokio::time::sleep_until(deadline).await;
Ok(())
}
async fn native_async_total(values: Vec<i64>, delayed: bool) -> Result<i64, NativeAsyncFailure> {
if delayed {
native_fallible_sleep(0).await?;
}
native_total(&values).map_err(Into::into)
}
// 仅作成本诊断:保留原始 Rust 参照,另测附带完成跟踪的相同双任务负载。
// 元组先放返回值再放令牌,未领取结果的析构顺序与生产运行库相同。
fn native_tracked<Failure, Work>(
native: &NativeTasks,
work: Work,
cancel: CancellationToken,
tracking: tokio_util::task::task_tracker::TaskTrackerToken,
) -> JoinHandle<(
Result<i64, Failure>,
tokio_util::task::task_tracker::TaskTrackerToken,
)>
where
Failure: From<Fault> + Send + 'static,
Work: std::future::Future<Output = Result<i64, Failure>> + Send + 'static,
{
let admission = native.tasks.clone().try_acquire_owned().unwrap();
let workers = native.workers.clone();
tokio::spawn(async move {
let tracking = tracking;
let result = tokio::select! {
biased;
result = work => result,
() = cancel.cancelled() => Err(task_fault("cancelled").into()),
};
drop(workers);
drop(admission);
(result, tracking)
})
}
fn reference_async_tracked(values: &[TaskCase]) -> Result<i64, Fault> {
reference_tracked(values, |values| async move { native_total(&values) })
}
fn reference_async_typed(values: &[TaskCase]) -> Result<i64, Fault> {
reference_tracked(values, |values| native_async_total(values, false))
}
fn reference_tracked<Failure, Work>(
values: &[TaskCase],
call: impl Fn(Vec<i64>) -> Work,
) -> Result<i64, Fault>
where
Failure: From<Fault> + Into<Fault> + Send + 'static,
Work: std::future::Future<Output = Result<i64, Failure>> + Send + 'static,
{
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let cancel = CancellationToken::new();
let tasks = tokio_util::task::TaskTracker::new();
let outcome = async {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let first = native_tracked(
&value.native,
call(value.values.clone()),
cancel.child_token(),
tasks.token(),
);
let second = native_tracked(
&value.native,
call(value.values.clone()),
cancel.child_token(),
tasks.token(),
);
let first = first
.await
.map_err(task_fault)
.and_then(|(result, _tracking)| result.map_err(Into::into));
if first.is_err() {
cancel.cancel();
}
let second = second
.await
.map_err(task_fault)
.and_then(|(result, _tracking)| result.map_err(Into::into));
let result = first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
}
.await;
cancel.cancel();
tasks.close();
tasks.wait().await;
outcome
})
}
fn generated_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.generated.run(async |_| 1i64).map_err(task_fault)
}
fn reference_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
Ok(value.native.runtime.block_on(async { 1i64 }))
}
fn generated_leaf_entry(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run_leaf(async { 1i64 }).map_err(task_fault)
}
fn generated_async_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = GENERATED_ASYNC(&value.values, scope)
.await
.map_err(task_fault)?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
.map_err(task_fault)?
}
fn reference_async_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let cancel = CancellationToken::new();
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let first = value
.native
.start(value.values.clone(), true, cancel.child_token());
let second = value
.native
.start(value.values.clone(), true, cancel.child_token());
let first = first.await.map_err(task_fault).and_then(|result| result);
if first.is_err() {
cancel.cancel();
}
let second = second.await.map_err(task_fault).and_then(|result| result);
let result = first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
cancel.cancel();
Ok(total)
})
}
fn generated_cancel_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = GENERATED_CANCEL(scope).await.map_err(task_fault)?;
total = total
.checked_add(result)
.ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
.map_err(task_fault)?
}
fn reference_cancel_steady(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value.native.runtime.block_on(async {
let root = CancellationToken::new();
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let admission = value.native.tasks.clone().try_acquire_owned().unwrap();
let cancel = root.child_token();
let child = cancel.clone();
let task = tokio::spawn(async move {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.unwrap();
tokio::select! {
biased;
() = tokio::time::sleep_until(deadline) => panic!("cancelled timer completed"),
() = child.cancelled() => (),
}
drop(admission);
});
cancel.cancel();
task.await.map_err(task_fault)?;
total = total
.checked_add(1)
.ok_or_else(|| task_fault("integer overflow"))?;
}
root.cancel();
Ok(total)
})
}
// 取消机制采用标准 JoinHandle::abort 的独立 Rust 对照;原 token 对照继续保留。
fn reference_cancel_abort(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.runtime.block_on(async {
let admission = value.native.tasks.clone().try_acquire_owned().unwrap();
let task = tokio::spawn(async move {
let _admission = admission;
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.ok_or_else(|| task_fault("sleep deadline"))?;
tokio::time::sleep_until(deadline).await;
Ok::<(), Fault>(())
});
task.abort();
match task.await {
Err(error) if error.is_cancelled() => Ok(1),
Err(error) => Err(task_fault(error)),
Ok(result) => { result?; Err(task_fault("cancelled timer completed")) }
}
})
}
fn lifecycle(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap() * 2 * CONTINUOUS_CALLS as i64;
assert_eq!(generated_async_steady(cases).unwrap(), expected);
assert_eq!(reference_async_steady(cases).unwrap(), expected);
assert_eq!(reference_async_tracked(cases).unwrap(), expected);
assert_eq!(reference_async_typed(cases).unwrap(), expected);
let native = &cases[0].native;
assert_eq!(
native
.runtime
.block_on(native_async_total(cases[0].values.clone(), true))
.map_err(Fault::from)
.unwrap(),
expected / CONTINUOUS_CALLS as i64 / 2
);
assert!(native.runtime.block_on(native_fallible_sleep(-1)).is_err());
eprintln!(
"result-layout: rust_typed_async={}",
std::mem::size_of::<Result<i64, NativeAsyncFailure>>()
);
assert_eq!(
generated_cancel_steady(cases).unwrap(),
CONTINUOUS_CALLS as i64
);
assert_eq!(
reference_cancel_steady(cases).unwrap(),
CONTINUOUS_CALLS as i64
);
cases[0]
.generated
.run(async |scope| {
eprintln!(
"task-layout: task={} optional_task={}",
std::mem::size_of::<dever_runtime::task::Task<()>>(),
std::mem::size_of::<Option<dever_runtime::task::Task<()>>>(),
);
eprintln!(
"future-layout: fault={} dever_pair={} rust_pair={} dever_cancel={} rust_cancel={}",
std::mem::size_of::<Fault>(),
std::mem::size_of_val(&GENERATED_ASYNC(&cases[0].values, scope)),
std::mem::size_of_val(&handwritten_pair(scope, &cases[0].values)),
std::mem::size_of_val(&GENERATED_CANCEL(scope)),
std::mem::size_of_val(&handwritten_cancel(scope)),
);
let result = GENERATED_ASYNC(&cases[0].values, scope).await;
eprintln!(
"result-layout: dever_async={} rust_async={}",
std::mem::size_of_val(&result),
std::mem::size_of::<Result<i64, Fault>>()
);
let result = GENERATED_CANCEL(scope).await;
eprintln!(
"result-layout: dever_cancel={} rust_cancel={}",
std::mem::size_of_val(&result),
std::mem::size_of::<Result<i64, Fault>>()
);
})
.unwrap();
compare(
"task_entry_ready",
cases,
100000,
generated_entry,
reference_entry,
|value| *value as u64,
);
compare(
"task_leaf_entry_ready",
cases,
100000,
generated_leaf_entry,
reference_entry,
|value| *value as u64,
);
compare(
"task_async_steady",
cases,
64,
generated_async_steady,
reference_async_steady,
|value| *value as u64,
);
compare(
"task_cancel_steady",
cases,
64,
generated_cancel_steady,
reference_cancel_steady,
|value| *value as u64,
);
compare_named(
"task_async_tracked_diagnostic",
cases,
64,
generated_async_steady,
reference_async_tracked,
|value| *value as u64,
"rust_tracked",
);
compare_named(
"task_async_typed_diagnostic",
cases,
64,
generated_async_steady,
reference_async_typed,
|value| *value as u64,
"rust_typed",
);
compare_named(
"task_cancel_abort_diagnostic",
cases,
1000,
generated_cancel,
reference_cancel_abort,
|value| *value as u64,
"rust_abort",
);
}
+225
View File
@@ -0,0 +1,225 @@
// 调度器保持存活,计时包含输入复制、提交、结果领取和作用域清理。
// 独立参照针对同样的一层双任务/worker/计时器,不能代表任意嵌套并发已验收。
use dever_runtime::task::{Limits, Runtime};
struct TaskCase {
generated: Runtime,
native: NativeTasks,
values: Vec<i64>,
}
fn new_runtime() -> Runtime {
Runtime::new(Limits {
threads: 2,
workers: 2,
tasks: 8,
})
.unwrap()
}
fn generated_sync(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_SYNC(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_sync(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.pair(&value.values, false))
}
fn generated_async(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_ASYNC(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_async(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.pair(&value.values, true))
}
fn generated_parallel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_PARALLEL(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_parallel(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.blocking(&value.values, false))
}
fn generated_blocking(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_BLOCKING(&value.values, scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_blocking(values: &[TaskCase]) -> Result<i64, Fault> {
values
.first()
.map_or(Ok(0), |value| value.native.blocking(&value.values, true))
}
fn generated_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run(async |scope| { GENERATED_CANCEL(scope).await })
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| value.native.cancel())
}
fn generated_timer(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else {
return Ok(0);
};
value
.generated
.run_leaf(GENERATED_TIMER())
.map_err(task_fault)?
.map_err(task_fault)
}
fn reference_timer(values: &[TaskCase]) -> Result<i64, Fault> {
values.first().map_or(Ok(0), |value| value.native.timer())
}
fn generated_fault(values: &[TaskCase]) -> Result<bool, Fault> {
Ok(generated_sync(values).is_err())
}
fn reference_fault(values: &[TaskCase]) -> Result<bool, Fault> {
Ok(reference_sync(values).is_err())
}
fn main() {
if std::env::args().any(|argument| argument == "--check-cancel-cleanup") {
check_cancel_cleanup();
return;
}
if std::env::args().any(|argument| argument == "--check-worker-cleanup") {
check_worker_cleanup();
return;
}
let cases = [TaskCase {
generated: new_runtime(),
native: NativeTasks::new(),
values: (0..128).collect(),
}];
let expected = native_total(&cases[0].values).unwrap();
assert_eq!(generated_sync(&cases).unwrap(), expected * 2);
assert_eq!(generated_async(&cases).unwrap(), expected * 2);
assert_eq!(generated_parallel(&cases).unwrap(), expected);
assert_eq!(generated_blocking(&cases).unwrap(), expected);
assert_eq!(generated_cancel(&cases).unwrap(), 1);
println!("case,sample,implementation,elapsed_ns,checksum");
compare(
"task_sync_pair",
&cases,
500,
generated_sync,
reference_sync,
|value| *value as u64,
);
compare(
"task_async_pair",
&cases,
1000,
generated_async,
reference_async,
|value| *value as u64,
);
compare(
"task_parallel",
&cases,
500,
generated_parallel,
reference_parallel,
|value| *value as u64,
);
compare(
"task_blocking",
&cases,
500,
generated_blocking,
reference_blocking,
|value| *value as u64,
);
compare(
"task_cancel",
&cases,
1000,
generated_cancel,
reference_cancel,
|value| *value as u64,
);
compare(
"task_timer_zero",
&cases,
10,
generated_timer,
reference_timer,
|value| *value as u64,
);
let failures = [TaskCase {
generated: new_runtime(),
native: NativeTasks::new(),
values: vec![i64::MAX, 1],
}];
assert!(generated_fault(&failures).unwrap());
assert!(generated_async(&failures).is_err());
assert!(reference_async(&failures).is_err());
assert!(shared_async(&failures).is_err());
assert!(shared_worker::<false>(&failures).is_err());
assert!(shared_worker::<true>(&failures).is_err());
assert!(generated_nested(&failures).is_err());
assert!(reference_nested(&failures).is_err());
compare(
"task_sync_fault",
&failures,
500,
generated_fault,
reference_fault,
|value| u64::from(*value),
);
assert!(generated_async_steady(&failures).is_err());
assert!(reference_async_steady(&failures).is_err());
assert!(reference_async_tracked(&failures).is_err());
assert!(reference_async_typed(&failures).is_err());
drop(failures);
lifecycle(&cases);
shared_runtime(&cases);
worker_steady(&cases);
worker_scoped(&cases);
cancel_scoped(&cases);
}
+144
View File
@@ -0,0 +1,144 @@
// 独立 Tokio 参照只实现本组一层任务工作负载,不调用 Dever 调度器。
// 任务和 worker 分别限流;遇到失败或取消仍领取所有 JoinHandle,已启动阻塞工作不强停。
use std::{sync::Arc, time::Duration};
use tokio::{runtime, sync::Semaphore, task::JoinHandle};
use tokio_util::sync::CancellationToken;
struct NativeTasks {
runtime: runtime::Runtime,
workers: Arc<Semaphore>,
tasks: Arc<Semaphore>,
}
fn task_fault(_: impl std::fmt::Debug) -> Fault {
&FaultInfo {
source: "task-reference",
line: 1,
column: 1,
message: "task failure",
}
}
fn native_total(values: &[i64]) -> Result<i64, Fault> {
values.iter().try_fold(0i64, |total, value| {
total
.checked_add(*value)
.ok_or_else(|| task_fault("integer overflow"))
})
}
impl NativeTasks {
fn new() -> Self {
Self {
runtime: runtime::Builder::new_multi_thread()
.worker_threads(2)
.max_blocking_threads(2)
.enable_time()
.build()
.unwrap(),
workers: Arc::new(Semaphore::new(2)),
tasks: Arc::new(Semaphore::new(8)),
}
}
fn start(
&self,
values: Vec<i64>,
asynchronous: bool,
cancel: CancellationToken,
) -> JoinHandle<Result<i64, Fault>> {
let admission = self.tasks.clone().try_acquire_owned().unwrap();
let workers = self.workers.clone();
tokio::spawn(async move {
let result = if asynchronous {
tokio::select! {
biased;
result = async { native_total(&values) } => result,
() = cancel.cancelled() => Err(task_fault("cancelled")),
}
} else {
let worker = tokio::select! {
biased;
() = cancel.cancelled() => return Err(task_fault("cancelled")),
worker = workers.acquire_owned() => worker.unwrap(),
};
let result = tokio::task::spawn_blocking(move || native_total(&values))
.await
.map_err(task_fault)?;
drop(worker);
result
};
drop(admission);
result
})
}
fn pair(&self, values: &[i64], asynchronous: bool) -> Result<i64, Fault> {
self.runtime.block_on(async {
let cancel = CancellationToken::new();
let first = self.start(values.to_vec(), asynchronous, cancel.child_token());
let second = self.start(values.to_vec(), asynchronous, cancel.child_token());
let first = first.await.map_err(task_fault).and_then(|value| value);
if first.is_err() {
cancel.cancel();
}
let second = second.await.map_err(task_fault).and_then(|value| value);
cancel.cancel();
first?
.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow"))
})
}
fn blocking(&self, values: &[i64], sleep: bool) -> Result<i64, Fault> {
let values = values.to_vec();
self.runtime.block_on(async {
let worker = self.workers.clone().acquire_owned().await.unwrap();
let result = tokio::task::spawn_blocking(move || {
if sleep {
std::time::Instant::now()
.checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(Duration::ZERO);
}
native_total(&values)
})
.await
.map_err(task_fault)?;
drop(worker);
result
})
}
fn cancel(&self) -> Result<i64, Fault> {
self.runtime.block_on(async {
let admission = self.tasks.clone().try_acquire_owned().unwrap();
let cancel = CancellationToken::new();
let child = cancel.child_token();
let task = tokio::spawn(async move {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::from_millis(60000))
.unwrap();
tokio::select! {
biased;
() = tokio::time::sleep_until(deadline) => panic!("cancelled timer completed"),
() = child.cancelled() => (),
}
drop(admission);
});
cancel.cancel();
task.await.map_err(task_fault)?;
Ok(1)
})
}
fn timer(&self) -> Result<i64, Fault> {
self.runtime.block_on(async {
let deadline = tokio::time::Instant::now()
.checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
tokio::time::sleep_until(deadline).await;
Ok(1)
})
}
}
+104
View File
@@ -0,0 +1,104 @@
// 独立 Tokio 参照补齐当前阻塞工作负载的收尾合同;不调用 Dever 运行库。
// 原始轻量参照继续保留,不将本组结果冒充原参照的性能提升。
use futures_util::FutureExt;
use std::panic::{AssertUnwindSafe, catch_unwind};
use tokio_util::task::TaskTracker;
impl NativeTasks {
fn worker_scope<Value>(
&self,
call: impl AsyncFnOnce(&TaskTracker) -> Value,
) -> Result<Value, Fault> {
let tasks = TaskTracker::new();
self.runtime.block_on(async {
let result = AssertUnwindSafe(async { call(&tasks).await }).catch_unwind().await;
tasks.close();
tasks.wait().await;
result.map_err(task_fault)
})
}
async fn tracked_blocking<Value: Send + 'static>(
&self,
tasks: &TaskTracker,
call: impl FnOnce() -> Value + Send + 'static,
) -> Result<Value, Fault> {
if tasks.is_closed() {
return Err(task_fault("closed worker scope"));
}
let permit = self.workers.clone().acquire_owned().await.unwrap();
let token = tasks.token();
let (result, _token) = tokio::task::spawn_blocking(move || {
let _permit = permit;
let result = catch_unwind(AssertUnwindSafe(call)).map_err(task_fault);
// 输出先析构,跟踪才结束;panic 载荷也在通知排空前释放。
(result, token)
}).await.map_err(task_fault)?;
result
}
}
fn reference_worker_scoped<const BLOCKING: bool, const CALLS: usize>(
values: &[TaskCase],
) -> Result<i64, Fault> {
if BLOCKING {
reference_worker_calls::<CALLS, _>(values, |owned| {
native_blocking_sleep(0)?;
native_total(&owned).map_err(NativeAsyncFailure::from)
})
} else {
reference_worker_calls::<CALLS, _>(values, |owned| native_total(&owned))
}
}
fn native_blocking_sleep(millis: i64) -> Result<(), NativeAsyncFailure> {
let fail = |message: &str| NativeAsyncFailure::Sleep(message.to_owned(), task_fault("sleep"));
let millis = u64::try_from(millis).map_err(|_| fail("sleep duration must be nonnegative"))?;
let duration = Duration::from_millis(millis);
std::time::Instant::now().checked_add(duration)
.ok_or_else(|| fail("sleep deadline is out of range"))?;
std::thread::sleep(duration);
Ok(())
}
// 保留实际业务错误及文本负载;不以填充字段强凑与生成代码相同的布局。
fn reference_worker_calls<const CALLS: usize, Failure: From<Fault> + Into<Fault> + Send + 'static>(
values: &[TaskCase],
call: impl FnOnce(Vec<i64>) -> Result<i64, Failure> + Copy + Send + 'static,
) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.worker_scope(async |tasks| {
let mut total = 0i64;
for _ in 0..CALLS {
let owned = value.values.clone();
let result = value.native.tracked_blocking(tasks, move || call(owned))
.await.map_err(Failure::from)??;
total = total.checked_add(result)
.ok_or_else(|| Failure::from(task_fault("integer overflow")))?;
}
Ok::<_, Failure>(total)
})?.map_err(Into::into)
}
fn worker_scoped(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap();
assert_eq!(reference_worker_scoped::<false, 1>(cases).unwrap(), expected);
assert_eq!(reference_worker_scoped::<true, 1>(cases).unwrap(), expected);
assert_eq!(reference_worker_scoped::<false, CONTINUOUS_CALLS>(cases).unwrap(), expected * CONTINUOUS_CALLS as i64);
assert_eq!(reference_worker_scoped::<true, CONTINUOUS_CALLS>(cases).unwrap(), expected * CONTINUOUS_CALLS as i64);
let failures = [TaskCase {
generated: new_runtime(), native: NativeTasks::new(), values: vec![i64::MAX, 1],
}];
assert!(reference_worker_scoped::<false, 1>(&failures).is_err());
assert!(reference_worker_scoped::<true, 1>(&failures).is_err());
assert!(reference_worker_scoped::<false, CONTINUOUS_CALLS>(&failures).is_err());
assert!(reference_worker_scoped::<true, CONTINUOUS_CALLS>(&failures).is_err());
compare_named("task_parallel_scoped", cases, 500,
generated_parallel, reference_worker_scoped::<false, 1>, |value| *value as u64, "rust_scoped");
compare_named("task_blocking_scoped", cases, 500,
generated_blocking, reference_worker_scoped::<true, 1>, |value| *value as u64, "rust_scoped");
compare_named("task_parallel_scoped_steady", cases, 64,
generated_worker_steady::<false>, reference_worker_scoped::<false, CONTINUOUS_CALLS>, |value| *value as u64, "rust_scoped");
compare_named("task_blocking_scoped_steady", cases, 64,
generated_worker_steady::<true>, reference_worker_scoped::<true, CONTINUOUS_CALLS>, |value| *value as u64, "rust_scoped");
}
+117
View File
@@ -0,0 +1,117 @@
// 手写 Rust 与 Dever 生成代码使用同一个运行时,隔离生成代码与运行时策略的成本。
// 原始直接 Tokio 参照继续保留;这里不调用任何生成函数或填充错误布局。
use dever_runtime::task::Scope;
async fn handwritten_pair(scope: &Scope<'_>, values: &[i64]) -> Result<i64, NativeAsyncFailure> {
let first_values = values.to_vec();
let first = scope
.spawn_leaf(move || native_async_total(first_values, false))
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))?;
let second_values = values.to_vec();
let second = match scope.spawn_leaf(move || native_async_total(second_values, false)) {
Ok(task) => task,
Err(error) => {
first.cancel();
let _ = first.wait().await;
return Err(task_fault(error).into());
}
};
let first = first.wait().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))
.and_then(|result| result);
if first.is_err() { second.cancel(); }
let second = second.wait().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))
.and_then(|result| result);
first?.checked_add(second?)
.ok_or_else(|| task_fault("integer overflow").into())
}
async fn handwritten_cancel(scope: &Scope<'_>) -> Result<i64, NativeAsyncFailure> {
let task = scope.spawn_leaf(|| native_fallible_sleep(60000))
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))?;
if let Some(result) = task.stop().await
.map_err(|error| NativeAsyncFailure::Fault(task_fault(error)))? {
result?;
}
Ok(1)
}
fn shared_async(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| handwritten_pair(scope, &value.values).await)
.map_err(task_fault)?.map_err(Into::into)
}
fn shared_cancel(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| handwritten_cancel(scope).await)
.map_err(task_fault)?.map_err(Into::into)
}
fn shared_worker<const SLEEP: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| {
let owned = value.values.clone();
scope.blocking(move || {
if SLEEP {
std::time::Instant::now()
.checked_add(std::time::Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(std::time::Duration::ZERO);
}
native_total(&owned)
}).await.map_err(task_fault)?
}).map_err(task_fault)?
}
// 两侧都在真实父任务中执行双子任务;同样复制输入,不改用更大的错误或状态。
fn nested<Work>(
values: &[TaskCase],
call: impl FnOnce(Scope<'static>, Vec<i64>) -> Work + Send + 'static,
) -> Result<i64, Fault>
where
Work: std::future::Future<Output = Result<i64, Fault>> + Send + 'static,
{
let Some(value) = values.first() else { return Ok(0); };
let owned = value.values.clone();
value.generated.run(async move |scope| {
let parent = scope.spawn(move |child| call(child, owned)).map_err(task_fault)?;
parent.wait().await.map_err(task_fault)?
}).map_err(task_fault)?
}
fn generated_nested(values: &[TaskCase]) -> Result<i64, Fault> {
nested(values, |scope, owned| async move {
GENERATED_ASYNC(&owned, &scope).await.map_err(task_fault)
})
}
fn reference_nested(values: &[TaskCase]) -> Result<i64, Fault> {
nested(values, |scope, owned| async move {
handwritten_pair(&scope, &owned).await.map_err(Into::into)
})
}
fn shared_runtime(cases: &[TaskCase]) {
compare_named(
"task_parallel_shared_runtime", cases, 500,
generated_parallel, shared_worker::<false>, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_blocking_shared_runtime", cases, 500,
generated_blocking, shared_worker::<true>, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_async_shared_runtime", cases, 1000,
generated_async, shared_async, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_cancel_shared_runtime", cases, 1000,
generated_cancel, shared_cancel, |value| *value as u64, "rust_runtime",
);
compare_named(
"task_nested_shared_runtime", cases, 1000,
generated_nested, reference_nested, |value| *value as u64, "rust_runtime",
);
}
+57
View File
@@ -0,0 +1,57 @@
// 每个入口连续提交并等待真实 worker;双方逐次复制输入、获取许可,不预先完成工作。
fn generated_worker_steady<const BLOCKING: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.generated.run(async |scope| {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let result = if BLOCKING {
GENERATED_BLOCKING(&value.values, scope).await.map_err(task_fault)?
} else {
GENERATED_PARALLEL(&value.values, scope).await?
};
total = total.checked_add(result).ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
}).map_err(task_fault)?
}
fn reference_worker_steady<const BLOCKING: bool>(values: &[TaskCase]) -> Result<i64, Fault> {
let Some(value) = values.first() else { return Ok(0); };
value.native.runtime.block_on(async {
let mut total = 0i64;
for _ in 0..CONTINUOUS_CALLS {
let owned = value.values.clone();
let permit = value.native.workers.clone().acquire_owned().await.unwrap();
let result = tokio::task::spawn_blocking(move || {
if BLOCKING {
std::time::Instant::now().checked_add(Duration::ZERO)
.ok_or_else(|| task_fault("sleep deadline"))?;
std::thread::sleep(Duration::ZERO);
}
native_total(&owned)
}).await.map_err(task_fault)?;
drop(permit);
total = total.checked_add(result?).ok_or_else(|| task_fault("integer overflow"))?;
}
Ok(total)
})
}
fn worker_steady(cases: &[TaskCase]) {
let expected = native_total(&cases[0].values).unwrap() * CONTINUOUS_CALLS as i64;
assert_eq!(generated_worker_steady::<false>(cases).unwrap(), expected);
assert_eq!(reference_worker_steady::<false>(cases).unwrap(), expected);
assert_eq!(generated_worker_steady::<true>(cases).unwrap(), expected);
assert_eq!(reference_worker_steady::<true>(cases).unwrap(), expected);
let failures = [TaskCase {
generated: new_runtime(), native: NativeTasks::new(), values: vec![i64::MAX, 1],
}];
assert!(generated_worker_steady::<false>(&failures).is_err());
assert!(reference_worker_steady::<false>(&failures).is_err());
assert!(generated_worker_steady::<true>(&failures).is_err());
assert!(reference_worker_steady::<true>(&failures).is_err());
compare("task_parallel_steady", cases, 64,
generated_worker_steady::<false>, reference_worker_steady::<false>, |value| *value as u64);
compare("task_blocking_steady", cases, 64,
generated_worker_steady::<true>, reference_worker_steady::<true>, |value| *value as u64);
}
+258
View File
@@ -0,0 +1,258 @@
fn chain_reference(values: &[String]) -> Result<String, Fault> {
let value = values.first().map(String::as_str).unwrap_or("");
let mut output = String::with_capacity(value.len() + value.len() + 3);
output.push('[');
output.push_str(value);
output.push(':');
output.push_str(value);
output.push(']');
Ok(output)
}
fn chain_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_CHAIN(values.first().map(String::as_str).unwrap_or(""))
}
// 与 Dever 的 str 函数边界一致;原切片入口参照仍保留。
fn chain_native(value: &str) -> Result<String, Fault> {
let mut output = String::with_capacity(value.len() + value.len() + 3);
output.push('[');
output.push_str(value);
output.push(':');
output.push_str(value);
output.push(']');
Ok(output)
}
fn chain_boundary(values: &[String]) -> Result<String, Fault> {
chain_native(values.first().map(String::as_str).unwrap_or(""))
}
fn pair_reference(values: &[String]) -> Result<String, Fault> {
let value = values.first().map(String::as_str).unwrap_or("");
let mut output = String::with_capacity(value.len() + value.len());
output.push_str(value);
output.push_str(value);
Ok(output)
}
fn pair_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_PAIR(values.first().map(String::as_str).unwrap_or(""))
}
fn temporary_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_TEMPORARY(values.first().map(String::as_str).unwrap_or(""))
}
fn temporary_reference(values: &[String]) -> Result<String, Fault> {
let value = values.first().map(String::as_str).unwrap_or("");
let mut output = String::with_capacity(value.len() + "后".len());
output.push_str(value);
output.push_str("后");
Ok(output)
}
fn forwarded_generated(values: &[String]) -> Result<String, Fault> {
GENERATED_FORWARDED(values.first().map(String::as_str).unwrap_or(""))
}
fn contains_reference(values: &[SEARCH_PAIR]) -> Result<i64, Fault> {
let mut total = 0i64;
for pair in values {
total = total
.checked_add(if pair.field0.contains(pair.field1.as_str()) {
120
} else {
0
})
.ok_or(
const {
&FaultInfo {
message: "integer arithmetic fault",
source: "reference",
line: 1,
column: 1,
}
},
)?;
}
Ok(total)
}
fn concatenate_text_reference(values: &[String]) -> Result<String, Fault> {
let mut output = String::new();
for value in values {
output.push_str(value);
}
Ok(output)
}
fn concatenate_bytes_reference(
values: &[dever_runtime::Bytes],
) -> Result<dever_runtime::Bytes, Fault> {
let mut output = Vec::new();
for value in values {
output.extend_from_slice(value);
}
Ok(output.into())
}
fn main() {
println!("case,sample,implementation,elapsed_ns,checksum");
for count in [8, 256, 4096] {
let values = vec!["雪山".to_owned(); count];
compare(
&format!("list_contains_{count}"),
&values,
2000,
list_contains_generated,
list_contains_reference,
|found| u64::from(*found),
);
}
// 双方使用同一批 String;独立 Rust 实现只调用标准库,排除分配布局和内置时钟差异。
for count in [512, 1024, 2048, 4096] {
let values: Vec<_> = (0..count)
.map(|_| {
let text = "compare-value-120".to_owned();
let pattern = text.to_uppercase().to_lowercase();
SEARCH_PAIR {
field0: text,
field1: pattern,
}
})
.collect();
compare(
&format!("text_contains_{count}"),
&values,
1000,
GENERATED_CONTAINS,
contains_reference,
|value| *value as u64,
);
}
let values: Vec<_> = [
("雪山🙂", "山"),
("abc", "z"),
("abc", ""),
("", "x"),
("", ""),
]
.into_iter()
.map(|(text, pattern)| SEARCH_PAIR {
field0: text.into(),
field1: pattern.into(),
})
.collect();
assert_eq!(GENERATED_CONTAINS(&values).unwrap(), 360);
assert_eq!(contains_reference(&values).unwrap(), 360);
for count in [512, 2048, 8192, 32768] {
let texts = vec!["x".to_owned(); count];
let bytes = (0..count)
.map(|_| dever_runtime::Bytes::from(vec![b'x']))
.collect::<Vec<_>>();
compare(
&format!("text_concat_{count}"),
&texts,
200,
GENERATED_TEXT_CONCAT,
concatenate_text_reference,
|value| value.len() as u64,
);
compare(
&format!("bytes_concat_{count}"),
&bytes,
200,
GENERATED_BYTES_CONCAT,
concatenate_bytes_reference,
|value| value.len() as u64,
);
}
for (name, value, count) in [
("text_chain_empty", String::new(), 300000),
("text_chain_small", "雪山".repeat(2), 300000),
("text_chain_large", "雪山".repeat(1024), 30000),
] {
let values = [value];
compare(
name,
&values,
count,
chain_generated,
chain_reference,
|value| value.len() as u64,
);
compare(
&format!("{name}_boundary"),
&values,
count,
chain_generated,
chain_boundary,
|value| value.len() as u64,
);
compare_borrowed(
name,
&values,
count,
chain_generated,
chain_reference,
|value| value.len() as u64,
);
}
compare(
"text_pair",
&["雪山".repeat(32)],
300000,
pair_generated,
pair_reference,
|value| value.len() as u64,
);
for (name, value, count) in [
("text_temporary_empty", String::new(), 300000),
("text_temporary_small", "雪山".repeat(2), 300000),
("text_temporary_medium", "雪山".repeat(32), 300000),
("text_temporary_large", "雪山".repeat(1024), 30000),
] {
let values = [value];
compare(
name,
&values,
count,
temporary_generated,
temporary_reference,
|value| value.len() as u64,
);
compare(
&format!("{name}_forwarded"),
&values,
count,
forwarded_generated,
temporary_reference,
|value| value.len() as u64,
);
compare_borrowed(
name,
&values,
count,
temporary_generated,
temporary_reference,
|value| value.len() as u64,
);
compare_borrowed(
&format!("{name}_forwarded"),
&values,
count,
forwarded_generated,
temporary_reference,
|value| value.len() as u64,
);
}
}
fn list_contains_generated(values: &[String]) -> Result<bool, Fault> {
GENERATED_LIST_CONTAINS(values, "absent")
}
fn list_contains_reference(values: &[String]) -> Result<bool, Fault> {
Ok(values.iter().any(|item| item.as_str() == "absent"))
}
+37
View File
@@ -0,0 +1,37 @@
use std::path::Path;
pub const DEPENDENCIES: &str =
"futures-util = { version = \"=0.3.34\", default-features = false, features = [\"std\"] }\n";
pub fn run(core: &Path) -> bool {
if !std::env::args().any(|argument| argument == "--transport") {
return false;
}
assert!(
!std::env::args().any(|argument| argument == "--memory"),
"transport uses asynchronous samples; --memory is unsupported"
);
let reference = [
include_str!("reference.rs"),
include_str!("session.rs"),
include_str!("samples.rs"),
]
.concat()
.replace(
"TRANSPORT_TLS_DIRECTORY",
&format!("{:?}", core.join("test/dever-tests/fixtures/tls")),
);
crate::compare(
core,
"transport-performance",
"transport",
&reference,
&[
("GENERATED_TRANSPORT_PLAIN", "main.servePlain"),
("GENERATED_TRANSPORT_TLS", "main.serveSecure"),
("GENERATED_TRANSPORT_EXCHANGE", "main.exchange"),
],
&[],
);
true
}
@@ -0,0 +1,57 @@
use dever_runtime::{
http, net,
task::{Runtime, Scope, Timed},
tls,
};
use std::{collections::BTreeSet, hint::black_box, sync::Arc, time::Instant};
async fn rust_transport_route(
request: http::Request,
_: Arc<Scope<'static>>,
) -> Result<http::Response, String> {
if request.method != "POST" || request.target != "/transport" {
return Ok(http::Response {
status: 404,
headers: vec![],
body: dever_runtime::Bytes::new(),
});
}
let mut body = Vec::with_capacity(6 + request.body.len());
body.extend_from_slice(b"reply:");
body.extend_from_slice(&request.body);
Ok(http::Response {
status: 201,
headers: vec![],
body: body.into(),
})
}
async fn rust_transport_serve(
listener: &net::Listener,
limits: &http::Limits,
scope: &Scope<'_>,
secure: Option<&tls::ServerTls>,
) -> Result<(), String> {
let result = if let Some(secure) = secure {
http::serve_tls::<_, _, false>(listener, limits, scope, secure, rust_transport_route).await
} else {
http::serve::<_, _, false>(listener, limits, scope, rust_transport_route).await
};
result.map_err(|error| error.message().to_owned())
}
async fn rust_transport_exchange(
client: &http::HttpClient,
payload: &dever_runtime::Bytes,
) -> Result<(i64, dever_runtime::Bytes), String> {
let response = client
.request(http::Request {
method: "POST".into(),
target: "/transport".into(),
headers: vec![],
body: payload.clone(),
})
.await
.map_err(|error| error.into_owned())?;
Ok((response.status, response.body))
}
+105
View File
@@ -0,0 +1,105 @@
fn transport_samples(
runtime: &Runtime,
certificates: &Arc<TransportCertificates>,
case: TransportCase,
smoke: bool,
) {
let name = case.name();
if let Ok(selected) = std::env::var("DEVER_BENCH_CASES")
&& !selected.split(',').any(|item| item == name)
{
return;
}
let batches = if case.cold || smoke { 2 } else { 16 };
if !smoke {
for generated in [true, false] {
black_box(transport_measure(
runtime,
certificates,
case,
generated,
2,
false,
));
}
}
eprintln!(
"{name}: concurrency={}, connections={}, requests_per_sample={}",
case.concurrency,
case.connections(),
batches * case.concurrency,
);
for sample in 0..if smoke { 1 } else { 15 } {
let mut order = [("dever", true), ("rust_runtime", false), ("control", true)];
order.rotate_left(sample % 3);
let mut expected = None;
for (implementation, generated) in order {
let (elapsed, checksum) =
transport_measure(runtime, certificates, case, generated, batches, !smoke);
if let Some(expected) = expected {
assert_eq!(checksum, expected);
}
expected = Some(checksum);
if !smoke {
println!("{name},{sample},{implementation},{elapsed},{checksum}");
}
}
}
if smoke {
println!("correctness smoke passed: {name}; no performance result collected");
}
}
fn main() {
let smoke = std::env::args().any(|argument| argument == "--smoke");
let fixture = std::path::Path::new(TRANSPORT_TLS_DIRECTORY);
let certificates = Arc::new(TransportCertificates {
root: std::fs::read(fixture.join("root.pem")).unwrap(),
server: tls::server(
&std::fs::read(fixture.join("server.pem")).unwrap(),
&std::fs::read(fixture.join("server-key.pem")).unwrap(),
)
.unwrap(),
});
let threads = std::thread::available_parallelism().map_or(1, |count| count.get().min(4));
let runtime = Runtime::new(dever_runtime::task::Limits {
threads,
workers: 1,
tasks: 256,
})
.unwrap();
eprintln!(
"transport: shared Dever runtime/Hyper/TLS; generation-layer comparison only; \
Linux dev Pod loopback; {threads} async workers (maximum 4), one blocking worker; \
HTTP/1 uses c connections; persistent HTTP/2 uses one connection and c streams; \
cold uses c fresh clients and fresh TLS session caches; \
cold timing includes connect/DNS (TLS localhost), full handshake and first response; \
server readiness is primed outside timing for both modes; \
persistent timing excludes the priming batch; payloads are fixed buffered bytes; \
elapsed_ns sums request batches, including request/handler/body collection; \
certificate/pool/server setup, exact-byte/socket checks and teardown are excluded"
);
if !smoke {
println!("case,sample,implementation,elapsed_ns,checksum");
}
for http2 in [false, true] {
for secure in [false, true] {
for concurrency in [1, 8, 32] {
for (cold, bytes) in [(true, 64), (false, 64), (false, 65536)] {
transport_samples(
&runtime,
&certificates,
TransportCase {
http2,
secure,
cold,
concurrency,
bytes,
},
smoke,
);
}
}
}
}
}
+256
View File
@@ -0,0 +1,256 @@
#[derive(Clone, Copy)]
struct TransportCase {
http2: bool,
secure: bool,
cold: bool,
concurrency: usize,
bytes: usize,
}
impl TransportCase {
fn connections(self) -> usize {
if self.cold || !self.http2 {
self.concurrency
} else {
1
}
}
fn limits(self, connections: usize, streams: usize) -> http::Limits {
http::Limits {
connections: connections as i64,
http2: self.http2.then_some(http::Http2Limits {
streams: streams as i64,
..Default::default()
}),
..Default::default()
}
}
fn name(self) -> String {
format!(
"http{}_{}_{}_c{}_b{}",
if self.http2 { 2 } else { 1 },
if self.secure { "tls" } else { "plain" },
if self.cold {
"cold_first_response"
} else {
"persistent"
},
self.concurrency,
self.bytes,
)
}
}
struct TransportCertificates {
root: Vec<u8>,
server: tls::ServerTls,
}
fn transport_clients(
case: TransportCase,
port: i64,
certificates: &TransportCertificates,
scope: &Scope<'_>,
) -> Vec<http::HttpClient> {
let origin = if case.secure {
format!("https://localhost:{port}")
} else {
format!("http://127.0.0.1:{port}")
};
let count = if case.cold { case.concurrency } else { 1 };
let limits = if case.cold {
case.limits(1, 1)
} else {
case.limits(case.connections(), case.concurrency)
};
(0..count)
.map(|_| {
let fresh_tls = tls::client(&certificates.root).unwrap();
http::client(
&origin,
&fresh_tls,
&limits,
&http::PoolLimits::default(),
scope,
)
.unwrap()
})
.collect()
}
async fn transport_batch(
generated: bool,
clients: &[http::HttpClient],
payloads: &[dever_runtime::Bytes],
) -> Vec<(i64, dever_runtime::Bytes)> {
futures_util::future::join_all(payloads.iter().enumerate().map(|(index, payload)| {
let client = &clients[index % clients.len()];
async move {
if generated {
GENERATED_TRANSPORT_EXCHANGE(black_box(client), black_box(payload))
.await
.map_err(|error| error.to_string())
} else {
rust_transport_exchange(black_box(client), black_box(payload)).await
}
}
}))
.await
.into_iter()
.map(Result::unwrap)
.collect()
}
fn transport_check(
replies: &[(i64, dever_runtime::Bytes)],
payloads: &[dever_runtime::Bytes],
) -> u64 {
assert_eq!(replies.len(), payloads.len());
let mut checksum = 0u64;
for ((status, body), payload) in replies.iter().zip(payloads) {
assert_eq!(*status, 201);
assert_eq!(body.len(), 6 + payload.len());
assert_eq!(&body[..6], b"reply:");
assert_eq!(&body[6..], payload.as_ref());
checksum += *status as u64 + body.iter().map(|byte| u64::from(*byte)).sum::<u64>();
}
black_box(checksum)
}
fn transport_connections(port: i64) -> BTreeSet<(String, String)> {
let endpoint = format!("0100007F:{port:04X}");
std::fs::read_to_string("/proc/self/net/tcp")
.expect("transport checks require the Linux dev Pod TCP table")
.lines()
.skip(1)
.filter_map(|line| {
let fields = line.split_whitespace().collect::<Vec<_>>();
assert!(fields.len() >= 10, "malformed Linux TCP table");
(fields[1] == endpoint && fields[3] == "01")
.then(|| (fields[2].to_owned(), fields[9].to_owned()))
})
.collect()
}
async fn transport_close(clients: Vec<http::HttpClient>) {
for client in clients {
client.close().await.unwrap();
assert!(rust_transport_exchange(&client, &dever_runtime::Bytes::from_static(b"closed")).await.is_err());
}
}
async fn transport_session(
scope: &Scope<'_>,
certificates: &TransportCertificates,
case: TransportCase,
generated: bool,
batches: usize,
timed: bool,
) -> (u128, u64) {
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
let port = listener.port().unwrap();
let incoming = listener.clone();
let server_tls = certificates.server.clone();
let server = scope
.spawn(move |child| async move {
let connections = case.connections() as i64;
let streams = case.concurrency as i64;
if generated && case.secure {
GENERATED_TRANSPORT_TLS(
&incoming,
case.http2,
connections,
streams,
&server_tls,
&child,
)
.await
.map_err(|error| error.to_string())
} else if generated {
GENERATED_TRANSPORT_PLAIN(&incoming, case.http2, connections, streams, &child)
.await
.map_err(|error| error.to_string())
} else {
rust_transport_serve(
&incoming,
&case.limits(case.connections(), case.concurrency),
&child,
case.secure.then_some(&server_tls),
)
.await
}
})
.unwrap();
let payloads = (0..case.concurrency)
.map(|slot| {
(0..case.bytes)
.map(|index| ((index + slot) % 251) as u8)
.collect::<Vec<_>>()
.into()
})
.collect::<Vec<dever_runtime::Bytes>>();
let mut clients = transport_clients(case, port, certificates, scope);
let replies = transport_batch(generated, &clients, &payloads).await;
transport_check(&replies, &payloads);
let connections = transport_connections(port);
assert_eq!(connections.len(), case.connections());
let persistent = if case.cold {
transport_close(clients).await;
clients = transport_clients(case, port, certificates, scope);
BTreeSet::new()
} else {
connections
};
let mut elapsed = 0;
let mut checksum = 0;
for batch in 0..batches {
if case.cold && batch != 0 {
clients = transport_clients(case, port, certificates, scope);
}
let start = timed.then(Instant::now);
let replies = transport_batch(generated, &clients, &payloads).await;
elapsed += start.map_or(0, |start| start.elapsed().as_nanos());
checksum += transport_check(&replies, &payloads);
let connections = transport_connections(port);
assert_eq!(connections.len(), case.connections());
if case.cold {
transport_close(std::mem::take(&mut clients)).await;
} else {
assert_eq!(connections, persistent, "persistent transport reconnected");
}
}
transport_close(clients).await;
listener.close().unwrap();
match server.timeout(5000).await.unwrap() {
Timed::Ready(result) => result.unwrap(),
Timed::Expired(_) => panic!("transport server did not drain"),
}
assert!(transport_connections(port).is_empty());
(elapsed, checksum)
}
fn transport_measure(
runtime: &Runtime,
certificates: &Arc<TransportCertificates>,
case: TransportCase,
generated: bool,
batches: usize,
timed: bool,
) -> (u128, u64) {
let certificates = certificates.clone();
runtime
.run(async move |scope| {
let session = scope
.spawn(move |child| async move {
transport_session(&child, &certificates, case, generated, batches, timed).await
})
.unwrap();
match session.timeout(30000).await.unwrap() {
Timed::Ready(result) => result,
Timed::Expired(_) => panic!("transport sample exceeded 30 seconds after cleanup"),
}
})
.unwrap()
}
+95
View File
@@ -0,0 +1,95 @@
//! 仅定位成功收发的同步成本,不作为完整资源合同的 Rust 参照。
use futures_util::{Sink, Stream};
use std::{
future::{Future, poll_fn},
pin::Pin,
sync::Mutex,
task::{Context, Poll, ready},
};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{Error, Message},
};
use tokio_util::sync::CancellationToken;
pub trait Transport: AsyncRead + AsyncWrite + Unpin + Send {}
impl<Value: AsyncRead + AsyncWrite + Unpin + Send> Transport for Value {}
pub type Boxed = Box<dyn Transport>;
type Wire = WebSocketStream<Boxed>;
pub struct Shared<const CANCEL: bool> {
wire: Mutex<Wire>,
read: tokio::sync::Mutex<()>,
write: tokio::sync::Mutex<()>,
closed: CancellationToken,
closing: CancellationToken,
}
impl<const CANCEL: bool> Shared<CANCEL> {
pub fn new(wire: Wire) -> Self {
Self {
wire: Mutex::new(wire),
read: tokio::sync::Mutex::new(()),
write: tokio::sync::Mutex::new(()),
closed: CancellationToken::new(),
closing: CancellationToken::new(),
}
}
fn poll<Value>(
&self,
context: &mut Context<'_>,
call: impl FnOnce(Pin<&mut Wire>, &mut Context<'_>) -> Poll<Value>,
) -> Poll<Value> {
let mut wire = self.wire.lock().unwrap();
call(Pin::new(&mut *wire), context)
}
async fn active<Value>(
&self,
work: impl Future<Output = Result<Value, Error>>,
) -> Result<Value, Error> {
if CANCEL {
tokio::select! {
biased;
() = self.closed.cancelled() => Err(Error::ConnectionClosed),
() = self.closing.cancelled() => Err(Error::ConnectionClosed),
result = work => result,
}
} else {
work.await
}
}
pub async fn send(&self, frame: Message) -> Result<(), Error> {
self.active(async {
let _direction = self.write.lock().await;
let mut frame = Some(frame);
poll_fn(|context| {
self.poll(context, |mut wire, context| {
ready!(wire.as_mut().poll_ready(context))?;
Poll::Ready(wire.start_send(frame.take().unwrap()))
})
})
.await?;
poll_fn(|context| self.poll(context, |wire, context| wire.poll_flush(context))).await
})
.await
}
pub async fn receive(&self) -> Result<Option<Message>, Error> {
self.active(async {
let _direction = self.read.lock().await;
poll_fn(|context| self.poll(context, |wire, context| wire.poll_next(context)))
.await
.transpose()
})
.await
}
pub async fn close(&mut self) {
self.wire.get_mut().unwrap().close(None).await.unwrap();
}
}
+114
View File
@@ -0,0 +1,114 @@
// 对照真实发送的生成适配层;双方共用运行库,分别记录发送与完整收发,逐字节校验在计时外。
use dever_runtime::{
http::websocket::{self as ws, Message, WebSocket},
task::{Limits, Runtime},
};
use std::{hint::black_box, time::Instant};
async fn send(generated: bool, borrowed: bool, socket: &WebSocket, message: &MESSAGE_TYPE) {
if generated && borrowed {
WS_SEND_MESSAGE(socket, message).await.unwrap();
return;
}
match message {
MESSAGE_TYPE::Variant0(text) if generated => WS_SEND_TEXT(socket, text).await.unwrap(),
MESSAGE_TYPE::Variant1(bytes) if generated => WS_SEND_BINARY(socket, bytes).await.unwrap(),
MESSAGE_TYPE::Variant0(text) => socket.send(Message::Text(text.clone())).await.unwrap(),
MESSAGE_TYPE::Variant1(bytes) => socket.send(Message::Binary(bytes.clone())).await.unwrap(),
_ => unreachable!(),
}
}
fn payload_size(message: &MESSAGE_TYPE) -> usize {
match message {
MESSAGE_TYPE::Variant0(text) => text.len(),
MESSAGE_TYPE::Variant1(bytes) => bytes.len(),
_ => unreachable!(),
}
}
fn check(received: Option<Message>, expected: &MESSAGE_TYPE) {
match (received, expected) {
(Some(Message::Text(actual)), MESSAGE_TYPE::Variant0(expected)) => {
assert_eq!(&actual, expected)
}
(Some(Message::Binary(actual)), MESSAGE_TYPE::Variant1(expected)) => {
assert_eq!(&actual, expected)
}
_ => panic!("WebSocket message changed"),
}
}
async fn ready(socket: &WebSocket, input: &MESSAGE_TYPE, count: usize) {
socket
.send(Message::Text(format!("roundtrip:{count}")))
.await
.unwrap();
send(false, false, socket, input).await;
assert_eq!(
socket.receive().await.unwrap(),
Some(Message::Text("ready".to_owned()))
);
}
fn main() {
if std::env::args().any(|argument| argument == "--peer") {
peer::run();
return;
}
let mut peer = peer::Peer::start(1);
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 128,
})
.unwrap();
println!("case,sample,implementation,elapsed_ns,checksum");
runtime.run(async |scope| {
// 双方轮流使用同一连接;固定协议对端在另一 CPU,隔离连接差异与线程争抢。
let socket = ws::connect("127.0.0.1", i64::from(peer.port), "/bench", &ws::Limits::default(), scope).await.unwrap();
for bytes in [64, 65536] {
for binary in [false, true] {
let input = if binary {
MESSAGE_TYPE::Variant1((0..bytes).map(|index| (index % 251) as u8).collect::<Vec<_>>().into())
} else {
MESSAGE_TYPE::Variant0("雪a".repeat(bytes / 4))
};
for borrowed in [false, true] {
let name = format!("ws_{}_{}_{}", if binary { "binary" } else { "text" }, if borrowed { "borrowed" } else { "temporary" }, bytes);
for generated in [true, false] {
ready(&socket, &input, 20).await;
for _ in 0..20 {
send(generated, borrowed, &socket, &input).await;
check(socket.receive().await.unwrap(), &input);
}
}
for sample in 0..15 {
let mut order = [("dever", true), ("rust_runtime", false), ("control", true)];
order.rotate_left(sample % 3);
for (implementation, generated) in order {
ready(&socket, &input, 200).await;
let mut sent = 0;
let mut roundtrip = 0;
let mut checksum = 0;
for _ in 0..200 {
let start = Instant::now();
send(generated, borrowed, &socket, black_box(&input)).await;
sent += start.elapsed().as_nanos();
let received = socket.receive().await.unwrap();
roundtrip += start.elapsed().as_nanos();
check(received, &input);
checksum += payload_size(black_box(&input));
}
assert_eq!(checksum, bytes * 200);
println!("{name}_send,{sample},{implementation},{sent},{checksum}");
println!("{name}_roundtrip,{sample},{implementation},{roundtrip},{checksum}");
}
}
}
}
}
socket.close(1000, "").await.unwrap();
}).unwrap();
peer.finish();
}
+120
View File
@@ -0,0 +1,120 @@
// 独立协议对端,不调用 Dever HTTP/WebSocket;绑到另一个 CPU,避免双方争抢工作线程。
use futures_util::{SinkExt, StreamExt};
use std::{
io::{BufRead, BufReader, Write},
process::{Child, Command, Stdio},
};
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{Message, protocol::WebSocketConfig},
};
pub fn config() -> WebSocketConfig {
WebSocketConfig::default()
.read_buffer_size(4096)
.write_buffer_size(0)
.max_write_buffer_size(1048576 + 4096)
.max_message_size(Some(1048576))
.max_frame_size(Some(1048576))
}
pub struct Peer {
child: Child,
pub port: u16,
}
impl Peer {
pub fn start(connections: usize) -> Self {
let cpu = std::env::var("DEVER_WS_PEER_CPU").expect("set a separate peer CPU");
let executable = std::env::var_os("DEVER_WS_PEER_BINARY")
.map(std::path::PathBuf::from)
.unwrap_or_else(|| std::env::current_exe().unwrap());
let mut child = Command::new("taskset")
.args(["-c", &cpu])
.arg(executable)
.arg("--peer")
.env("DEVER_WS_PEER_CONNECTIONS", connections.to_string())
.stdout(Stdio::piped())
.spawn()
.unwrap();
let mut ready = String::new();
BufReader::new(child.stdout.take().unwrap())
.read_line(&mut ready)
.unwrap();
let port = ready.trim().parse().expect("peer did not report its port");
Self { child, port }
}
pub fn finish(&mut self) {
assert!(self.child.wait().unwrap().success());
}
}
impl Drop for Peer {
fn drop(&mut self) {
// 断言失败也回收本次测试创建的子进程。
let _ = self.child.kill();
let _ = self.child.wait();
}
}
async fn serve(mut socket: WebSocketStream<TcpStream>) {
while let Some(command) = socket.next().await {
let command = command.unwrap();
if command.is_close() {
socket.flush().await.unwrap();
return;
}
let (mode, count) = command.to_text().unwrap().split_once(':').unwrap();
let count: usize = count.parse().unwrap();
assert!(mode == "batch" || mode == "roundtrip");
let expected = socket.next().await.unwrap().unwrap();
socket.send(Message::Text("ready".into())).await.unwrap();
for _ in 0..count {
let actual = socket.next().await.unwrap().unwrap();
assert_eq!(actual, expected);
if mode == "roundtrip" {
socket.send(actual).await.unwrap();
}
}
if mode == "batch" {
socket.send(expected).await.unwrap();
}
}
}
pub fn run() {
let connections: usize = std::env::var("DEVER_WS_PEER_CONNECTIONS")
.expect("set expected peer connections")
.parse()
.unwrap();
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
runtime.block_on(async {
tokio::time::timeout(std::time::Duration::from_secs(120), async {
let listener = TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
println!("{}", listener.local_addr().unwrap().port());
std::io::stdout().flush().unwrap();
let mut sessions = Vec::new();
for _ in 0..connections {
let (stream, _) = listener.accept().await.unwrap();
stream.set_nodelay(true).unwrap();
sessions.push(tokio::spawn(async move {
let socket =
tokio_tungstenite::accept_async_with_config(stream, Some(config()))
.await
.unwrap();
serve(socket).await;
}));
}
for session in sessions {
session.await.unwrap();
}
})
.await
.expect("protocol peer timed out");
});
}
+269
View File
@@ -0,0 +1,269 @@
// 成功会话的独立协议参照。原共享运行库基准保留;这里不声称覆盖别名、取消和资源归属合同。
use dever_runtime::{
http::websocket::{self as ws, Message, WebSocket},
task::{Limits, Runtime},
};
use futures_util::{SinkExt, StreamExt};
use std::{
hint::black_box,
time::{Duration, Instant},
};
use tokio::net::TcpStream;
use tokio_tungstenite::{WebSocketStream, tungstenite::Message as Frame};
enum Client {
Dever(WebSocket),
Rust(WebSocketStream<TcpStream>),
Boxed(WebSocketStream<layers::Boxed>),
Locked(layers::Shared<false>),
Cancelled(layers::Shared<true>),
Shared {
socket: shared::Socket,
_owner: shared::Owner,
},
}
impl Client {
async fn send(&mut self, value: &MESSAGE_TYPE) {
if let Self::Dever(socket) = self {
match value {
MESSAGE_TYPE::Variant0(text) => WS_SEND_TEXT(socket, text).await.unwrap(),
MESSAGE_TYPE::Variant1(bytes) => WS_SEND_BINARY(socket, bytes).await.unwrap(),
_ => unreachable!(),
}
return;
}
// 所有参照采用同样的借用输入、拥有消息及写期限。
let frame = match value {
MESSAGE_TYPE::Variant0(text) => Frame::Text(text.clone().into()),
MESSAGE_TYPE::Variant1(bytes) => Frame::Binary(bytes.clone()),
_ => unreachable!(),
};
if let Self::Shared { socket, .. } = self {
socket.send(frame).await.unwrap();
return;
}
assert!(frame.len() <= 1048576);
tokio::time::timeout(Duration::from_millis(5000), async {
match self {
Self::Rust(socket) => socket.send(frame).await,
Self::Boxed(socket) => socket.send(frame).await,
Self::Locked(socket) => socket.send(frame).await,
Self::Cancelled(socket) => socket.send(frame).await,
Self::Dever(_) => unreachable!(),
Self::Shared { .. } => unreachable!(),
}
})
.await
.unwrap()
.unwrap();
}
async fn receive(&mut self) -> Message {
if let Self::Dever(socket) = self {
return socket.receive().await.unwrap().unwrap();
}
let frame = if let Self::Shared { socket, .. } = self {
socket.receive().await.unwrap().unwrap()
} else {
tokio::time::timeout(Duration::from_millis(60000), async {
match self {
Self::Rust(socket) => socket.next().await.transpose(),
Self::Boxed(socket) => socket.next().await.transpose(),
Self::Locked(socket) => socket.receive().await,
Self::Cancelled(socket) => socket.receive().await,
Self::Dever(_) => unreachable!(),
Self::Shared { .. } => unreachable!(),
}
})
.await
.unwrap()
.unwrap()
.unwrap()
};
match frame {
Frame::Text(value) => Message::Text(value.to_string()),
Frame::Binary(value) => Message::Binary(value),
_ => panic!("unexpected protocol frame"),
}
}
async fn close(&mut self) {
match self {
Self::Dever(socket) => socket.close(1000, "").await.unwrap(),
Self::Rust(socket) => socket.close(None).await.unwrap(),
Self::Boxed(socket) => socket.close(None).await.unwrap(),
Self::Locked(socket) => socket.close().await,
Self::Cancelled(socket) => socket.close().await,
Self::Shared { socket, .. } => socket.close(1000, "").await.unwrap(),
}
}
}
fn check(actual: Message, expected: &MESSAGE_TYPE) {
match (actual, expected) {
(Message::Text(actual), MESSAGE_TYPE::Variant0(expected)) => assert_eq!(&actual, expected),
(Message::Binary(actual), MESSAGE_TYPE::Variant1(expected)) => {
assert_eq!(&actual, expected)
}
_ => panic!("payload changed"),
}
}
async fn measure(client: &mut Client, input: &MESSAGE_TYPE, mode: &str, count: usize) -> u128 {
client
.send(&MESSAGE_TYPE::Variant0(format!("{mode}:{count}")))
.await;
client.send(input).await;
check(
client.receive().await,
&MESSAGE_TYPE::Variant0("ready".to_owned()),
);
if mode == "batch" {
// 批量发送后单独收确认,避免把每条消息的回声等待计入发送成本。
let start = Instant::now();
for _ in 0..count {
client.send(black_box(input)).await;
}
let elapsed = start.elapsed().as_nanos();
check(client.receive().await, input);
elapsed
} else {
let mut elapsed = 0;
for _ in 0..count {
let start = Instant::now();
client.send(black_box(input)).await;
let actual = client.receive().await;
elapsed += start.elapsed().as_nanos();
check(actual, input);
}
elapsed
}
}
fn main() {
if std::env::args().any(|argument| argument == "--peer") {
peer::run();
return;
}
let mut peer = peer::Peer::start(2);
let reference = std::env::var("DEVER_WS_REFERENCE").unwrap_or_else(|_| "raw".to_owned());
let label = match reference.as_str() {
"raw" => "rust_protocol",
"boxed" => "rust_boxed_diagnostic",
"locked" => "rust_locked_diagnostic",
"cancelled" => "rust_cancelled_diagnostic",
"shared" => "rust_shared",
_ => panic!("unknown WebSocket reference"),
};
let runtime = Runtime::new(Limits {
threads: 1,
workers: 1,
tasks: 128,
})
.unwrap();
println!("case,sample,implementation,elapsed_ns,checksum");
runtime
.run(async |scope| {
tokio::time::timeout(Duration::from_secs(120), async {
let socket = ws::connect(
"127.0.0.1",
i64::from(peer.port),
"/bench",
&ws::Limits::default(),
scope,
)
.await
.unwrap();
let stream = TcpStream::connect(("127.0.0.1", peer.port)).await.unwrap();
stream.set_nodelay(true).unwrap();
let url = format!("ws://127.0.0.1:{}/bench", peer.port);
let control = if reference == "raw" || reference == "shared" {
let (wire, _) = tokio_tungstenite::client_async_with_config(
url,
stream,
Some(peer::config()),
)
.await
.unwrap();
if reference == "shared" {
let (socket, owner) = shared::Socket::new(
wire,
shared::Limits {
message: 1048576,
idle: Duration::from_millis(60000),
write: Duration::from_millis(5000),
},
);
Client::Shared {
socket,
_owner: owner,
}
} else {
Client::Rust(wire)
}
} else {
let stream: layers::Boxed = Box::new(stream);
let (wire, _) = tokio_tungstenite::client_async_with_config(
url,
stream,
Some(peer::config()),
)
.await
.unwrap();
match reference.as_str() {
"boxed" => Client::Boxed(wire),
"locked" => Client::Locked(layers::Shared::new(wire)),
"cancelled" => Client::Cancelled(layers::Shared::new(wire)),
_ => unreachable!(),
}
};
let mut clients = [Client::Dever(socket), control];
for bytes in [64, 65536] {
for binary in [false, true] {
let input = if binary {
MESSAGE_TYPE::Variant1(
(0..bytes)
.map(|index| (index % 251) as u8)
.collect::<Vec<_>>()
.into(),
)
} else {
MESSAGE_TYPE::Variant0("雪a".repeat(bytes / 4))
};
for mode in ["batch", "roundtrip"] {
let name = format!(
"ws_protocol_{}_{bytes}_{mode}",
if binary { "binary" } else { "text" }
);
for client in &mut clients {
black_box(measure(client, &input, mode, 20).await);
}
for sample in 0..15 {
let mut order = [("dever", 0), (label, 1), ("control", 0)];
order.rotate_left(sample % 3);
if sample / 3 % 2 == 1 {
order.swap(0, 1);
}
for (implementation, index) in order {
let elapsed =
measure(&mut clients[index], &input, mode, 200).await;
println!(
"{name},{sample},{implementation},{elapsed},{}",
bytes * 200
);
}
}
}
}
}
for client in &mut clients {
client.close().await;
}
})
.await
.expect("protocol comparison timed out");
})
.unwrap();
peer.finish();
}
@@ -0,0 +1,212 @@
// 独立 Rust 共享会话参照,不调用 Dever 运行库;覆盖收发、期限、关闭和所有权退出。
// TCP/WebSocket 握手在计时外完成;此参照不代表 TLS、HTTP 升级或整个 Scope 系统。
mod wire;
use futures_util::{SinkExt, StreamExt};
use std::{
borrow::Cow,
future::Future,
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use tokio::{net::TcpStream, sync::Mutex as Direction};
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{
Message,
protocol::{CloseFrame, frame::coding::CloseCode},
},
};
use tokio_util::sync::CancellationToken;
use wire::Wire;
pub type Error = Cow<'static, str>;
#[derive(Clone, Copy)]
pub struct Limits {
pub message: usize,
pub idle: Duration,
pub write: Duration,
}
struct State {
socket: Mutex<Option<WebSocketStream<TcpStream>>>,
reader: Direction<()>,
writer: Direction<()>,
closing: CancellationToken,
ended: AtomicBool,
limits: Limits,
}
impl State {
fn abort(&self) {
self.closing.cancel();
self.socket.lock().unwrap().take();
}
async fn active<Value>(
&self,
work: impl Future<Output = Result<Value, Error>>,
) -> Result<Value, Error> {
tokio::select! {
biased;
() = self.closing.cancelled() => Err("WebSocket is closing".into()),
result = work => result,
}
}
}
// 所有者退出同步释放协议连接;保留别名不能延长连接生命。
pub struct Owner(Arc<State>);
impl Drop for Owner {
fn drop(&mut self) {
self.0.abort();
}
}
struct Writing<'socket>(Option<&'socket State>);
impl Drop for Writing<'_> {
fn drop(&mut self) {
if let Some(state) = self.0 {
state.abort();
}
}
}
#[derive(Clone)]
pub struct Socket(Arc<State>);
impl Socket {
pub fn new(socket: WebSocketStream<TcpStream>, limits: Limits) -> (Self, Owner) {
let state = Arc::new(State {
socket: Mutex::new(Some(socket)),
reader: Direction::new(()),
writer: Direction::new(()),
closing: CancellationToken::new(),
ended: AtomicBool::new(false),
limits,
});
(Self(state.clone()), Owner(state))
}
pub async fn send(&self, message: Message) -> Result<(), Error> {
let limit = match message {
Message::Text(_) | Message::Binary(_) => self.0.limits.message,
Message::Ping(_) | Message::Pong(_) => 125,
_ => return Err("use close for the closing handshake".into()),
};
if message.len() > limit {
return Err("WebSocket message exceeds byte limit".into());
}
self.0
.active(async {
tokio::time::timeout(self.0.limits.write, async {
let _writer = self.0.writer.lock().await;
let mut writing = Writing(Some(&self.0));
Wire(&self.0.socket).send(message).await.map_err(failure)?;
writing.0 = None;
Ok(())
})
.await
.map_err(|_| Error::from("WebSocket write timed out"))?
})
.await
}
pub async fn receive(&self) -> Result<Option<Message>, Error> {
if self.0.ended.load(Ordering::Acquire) {
return Ok(None);
}
let result = self
.0
.active(async {
tokio::time::timeout(self.0.limits.idle, async {
let _reader = self.0.reader.lock().await;
if self.0.ended.load(Ordering::Acquire) {
return Ok(None);
}
let message = Wire(&self.0.socket)
.next()
.await
.transpose()
.map_err(failure)?;
match message {
Some(Message::Ping(_)) => {
self.reply().await?;
Ok(message)
}
Some(Message::Close(_)) | None => {
let _closing = Writing(Some(&self.0));
self.reply().await?;
self.0.ended.store(true, Ordering::Release);
Ok(None)
}
_ => Ok(message),
}
})
.await
.map_err(|_| Error::from("WebSocket receive timed out"))?
})
.await;
if result.is_err() && !self.0.closing.is_cancelled() {
self.0.abort();
}
result
}
async fn reply(&self) -> Result<(), Error> {
let _writer = self.0.writer.lock().await;
let mut writing = Writing(Some(&self.0));
tokio::time::timeout(self.0.limits.write, Wire(&self.0.socket).flush())
.await
.map_err(|_| Error::from("WebSocket reply timed out"))?
.map_err(failure)?;
writing.0 = None;
Ok(())
}
pub async fn close(&self, code: u16, reason: &str) -> Result<(), Error> {
let code = CloseCode::from(code);
if !code.is_allowed() || reason.len() > 123 {
return Err("invalid WebSocket close frame".into());
}
if self.0.closing.is_cancelled() {
return Err("WebSocket is closed".into());
}
self.0.closing.cancel();
let _closing = Writing(Some(&self.0));
tokio::time::timeout(self.0.limits.write, async {
let writer = self.0.writer.lock().await;
Wire(&self.0.socket)
.send(Message::Close(Some(CloseFrame {
code,
reason: reason.to_owned().into(),
})))
.await
.map_err(failure)?;
drop(writer);
let _reader = self.0.reader.lock().await;
loop {
let message = Wire(&self.0.socket)
.next()
.await
.transpose()
.map_err(failure)?;
Wire(&self.0.socket).flush().await.map_err(failure)?;
if matches!(message, Some(Message::Close(_)) | None) {
self.0.ended.store(true, Ordering::Release);
return Ok(());
}
}
})
.await
.map_err(|_| Error::from("WebSocket close timed out"))?
}
}
fn failure(error: tokio_tungstenite::tungstenite::Error) -> Error {
error.to_string().into()
}
@@ -0,0 +1,58 @@
use futures_util::{Sink, Stream};
use std::{
pin::Pin,
sync::Mutex,
task::{Context, Poll},
};
use tokio::net::TcpStream;
use tokio_tungstenite::{
WebSocketStream,
tungstenite::{Error, Message},
};
// 共享引用仅在一次协议调用内借用连接;send/next 的状态机由 futures-util 提供。
pub(super) struct Wire<'socket>(pub &'socket Mutex<Option<WebSocketStream<TcpStream>>>);
impl Wire<'_> {
fn with<Value>(
&self,
call: impl FnOnce(Pin<&mut WebSocketStream<TcpStream>>) -> Value,
) -> Result<Value, Error> {
let mut socket = self.0.lock().unwrap();
socket
.as_mut()
.map(|socket| call(Pin::new(socket)))
.ok_or(Error::ConnectionClosed)
}
}
impl Sink<Message> for Wire<'_> {
type Error = Error;
fn poll_ready(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.with(|socket| socket.poll_ready(context))?
}
fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Error> {
self.with(|socket| socket.start_send(message))?
}
fn poll_flush(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.with(|socket| socket.poll_flush(context))?
}
fn poll_close(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Result<(), Error>> {
self.with(|socket| socket.poll_close(context))?
}
}
impl Stream for Wire<'_> {
type Item = Result<Message, Error>;
fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.with(|socket| socket.poll_next(context)) {
Ok(result) => result,
Err(error) => Poll::Ready(Some(Err(error))),
}
}
}