//! 同一负载分别链接改造前后源码;衡量运行库和编译器成本,不冒充独立 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::>(); // 原 128 次重复超出 Hyper 的 URI 长度上限;本场景只测可接收的请求。 let text = "value%2B%E9%9B%AA".repeat(96); let query = names .iter() .map(|name| format!("{name}={text}")) .collect::>() .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 = 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::>(); let sites = names .iter() .map(|name| { ( name.clone(), serde_json::json!({"path":name,"hosts":["fixture.test"]}), ) }) .collect::>(); 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::>(); 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))) }); }