Files
core/test/rust/performance/costs/mod.rs
T
2026-11-03 03:32:07 +00:00

263 lines
8.6 KiB
Rust

//! 同一负载分别链接改造前后源码;衡量运行库和编译器成本,不冒充独立 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)))
});
}