263 lines
8.6 KiB
Rust
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)))
|
|
});
|
|
}
|