mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 04:01:11 +08:00
perf: 新增 Plan C Box 化设计门规格与分配成本测量装置
This commit is contained in:
Generated
+1
@@ -1142,6 +1142,7 @@ name = "oxide_cli"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
|
"bumpalo",
|
||||||
"clap",
|
"clap",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_code_cache",
|
"oxide_code_cache",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ oxide_compiler = { path = "../oxide_compiler" }
|
|||||||
oxide_vm = { path = "../oxide_vm" }
|
oxide_vm = { path = "../oxide_vm" }
|
||||||
oxide_kernel = { path = "../oxide_kernel" }
|
oxide_kernel = { path = "../oxide_kernel" }
|
||||||
oxide_log = { path = "../oxide_log" }
|
oxide_log = { path = "../oxide_log" }
|
||||||
|
bumpalo.workspace = true
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
rustyline.workspace = true
|
rustyline.workspace = true
|
||||||
ansi_term.workspace = true
|
ansi_term.workspace = true
|
||||||
|
|||||||
@@ -0,0 +1,188 @@
|
|||||||
|
//! 分配成本 go/no-go 测量装置(Plan C 设计门,行为中性)。
|
||||||
|
//!
|
||||||
|
//! 两个测量面:
|
||||||
|
//! - 微基准:同一对象尺寸(`JsObject` 定长头 136B)下,现分配路径
|
||||||
|
//! (bumpalo arena 分配加对象表登记)对新路径(堆 `Box::into_raw` 加对象表
|
||||||
|
//! 登记)的逐次成本,单线程,多轮取中位;
|
||||||
|
//! - 三例墙时间:gc_object / gc_array / mem_closure_chain 三个分配密集用例,
|
||||||
|
//! 与 js 压力基准同一 harness(编译一次、预热、每迭代新 VM 执行),记当前
|
||||||
|
//! 中位墙时间并对基线(`benchmark_baseline.json`)报差值。
|
||||||
|
//!
|
||||||
|
//! 本文件只测量不改引擎执行路径;退出码仅由用例执行成败驱动,go/no-go 判定
|
||||||
|
//! 由主 Agent 依据落档数字与噪声带作出。复跑命令:
|
||||||
|
//! `oxide bench --mode leak --filter alloc_ab`。
|
||||||
|
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use oxide_compiler::compiler::{compiled_module_hash, Compiler};
|
||||||
|
use oxide_kernel::kernel::KernelCore;
|
||||||
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_types::object::JsObject;
|
||||||
|
use oxide_types::value::JsValue;
|
||||||
|
use oxide_vm::vm_pool::VmPool;
|
||||||
|
|
||||||
|
use crate::bench::baseline::load_baseline;
|
||||||
|
use crate::bench::BenchConfig;
|
||||||
|
|
||||||
|
/// 微基准固定分配次数:10 万对象,对象尺寸 136B,单轮总量约 13.6MB,
|
||||||
|
/// 足以摊薄计时开销、测出逐次分配成本。
|
||||||
|
const MICRO_ALLOC_N: usize = 100_000;
|
||||||
|
/// 微基准轮数:每路径三轮取中位,抗单轮噪声。
|
||||||
|
const MICRO_ROUNDS: usize = 3;
|
||||||
|
|
||||||
|
/// 三个分配密集用例(设计门回归面):对象 churn、数组 churn、闭包链留存。
|
||||||
|
const WALL_CASES: [&str; 3] = ["gc_object", "gc_array", "mem_closure_chain"];
|
||||||
|
|
||||||
|
/// 运行分配成本 go/no-go 测量装置:先微基准,再三例墙时间。
|
||||||
|
pub fn run_alloc_ab(config: &BenchConfig, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
|
||||||
|
let micro = alloc_strategy_microbench();
|
||||||
|
eprintln!(
|
||||||
|
"[alloc_ab] microbench n={MICRO_ALLOC_N} (JsObject 136B, 单线程, 三轮中位): \
|
||||||
|
bump={:.2} ns/alloc, box={:.2} ns/alloc, delta={:+.2} ns/alloc; \
|
||||||
|
释放相 bump={:.2} ns/alloc, box={:.2} ns/alloc",
|
||||||
|
micro.bump_alloc,
|
||||||
|
micro.box_alloc,
|
||||||
|
micro.box_alloc - micro.bump_alloc,
|
||||||
|
micro.bump_free,
|
||||||
|
micro.box_free
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut all_ok = true;
|
||||||
|
for case in WALL_CASES {
|
||||||
|
match run_wall_case(case, config, kernel, pool) {
|
||||||
|
Ok((wall, baseline)) => match baseline {
|
||||||
|
Some(b) => eprintln!(
|
||||||
|
"[alloc_ab] wall {case}: current={wall:.2} ms, baseline={b:.2} ms, delta={:+.2} ms ({:+.1}%)",
|
||||||
|
wall - b,
|
||||||
|
(wall - b) / b.max(1e-9) * 100.0
|
||||||
|
),
|
||||||
|
None => eprintln!("[alloc_ab] wall {case}: current={wall:.2} ms (无基线条目)"),
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("[alloc_ab] wall {case} 执行失败: {e}");
|
||||||
|
all_ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if all_ok {
|
||||||
|
ExitCode::SUCCESS
|
||||||
|
} else {
|
||||||
|
ExitCode::FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 微基准结果:两路径的分配相与释放相成本(ns/alloc,三轮中位)。
|
||||||
|
struct MicroResult {
|
||||||
|
bump_alloc: f64,
|
||||||
|
bump_free: f64,
|
||||||
|
box_alloc: f64,
|
||||||
|
box_free: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 分配策略微基准:同一对象尺寸(`JsObject::new_empty`,136B)下,
|
||||||
|
/// 路径 A 为现分配路径(bumpalo arena 分配加对象表登记),路径 B 为新路径
|
||||||
|
/// (堆 `Box::into_raw` 加对象表登记);释放相 A 为整 arena 归还(现 `reset`
|
||||||
|
/// 换新 Bump 同构),B 为逐对象 `Box::from_raw`。单线程稳态,ns/alloc。
|
||||||
|
fn alloc_strategy_microbench() -> MicroResult {
|
||||||
|
let mut bump_alloc: Vec<f64> = Vec::new();
|
||||||
|
let mut bump_free: Vec<f64> = Vec::new();
|
||||||
|
let mut box_alloc: Vec<f64> = Vec::new();
|
||||||
|
let mut box_free: Vec<f64> = Vec::new();
|
||||||
|
|
||||||
|
for _ in 0..MICRO_ROUNDS {
|
||||||
|
// 路径 A:bumpalo arena 分配加对象表登记(现 alloc_object 路径)。
|
||||||
|
let bump = bumpalo::Bump::new();
|
||||||
|
let mut arena_ptrs: Vec<*mut JsObject> = Vec::with_capacity(MICRO_ALLOC_N);
|
||||||
|
let t0 = Instant::now();
|
||||||
|
for _ in 0..MICRO_ALLOC_N {
|
||||||
|
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
|
||||||
|
arena_ptrs.push(bump.alloc(obj));
|
||||||
|
}
|
||||||
|
bump_alloc.push(t0.elapsed().as_secs_f64() * 1e9 / MICRO_ALLOC_N as f64);
|
||||||
|
let t0 = Instant::now();
|
||||||
|
std::hint::black_box(&arena_ptrs);
|
||||||
|
// 整 arena 归还:现释放路径(epoch.reset 换新 Bump 同构)。
|
||||||
|
drop(bump);
|
||||||
|
bump_free.push(t0.elapsed().as_secs_f64() * 1e9 / MICRO_ALLOC_N as f64);
|
||||||
|
|
||||||
|
// 路径 B:堆 Box 分配加对象表登记(Box 化后新路径)。
|
||||||
|
let mut box_ptrs: Vec<*mut JsObject> = Vec::with_capacity(MICRO_ALLOC_N);
|
||||||
|
let t0 = Instant::now();
|
||||||
|
for _ in 0..MICRO_ALLOC_N {
|
||||||
|
let obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined());
|
||||||
|
box_ptrs.push(Box::into_raw(Box::new(obj)));
|
||||||
|
}
|
||||||
|
box_alloc.push(t0.elapsed().as_secs_f64() * 1e9 / MICRO_ALLOC_N as f64);
|
||||||
|
let t0 = Instant::now();
|
||||||
|
// 逐对象释放:Box 化后新释放路径(恰好一次约束在路径上)。
|
||||||
|
for &p in &box_ptrs {
|
||||||
|
// SAFETY: p 来自本循环的 Box::into_raw,无其他别名,恰好释放一次。
|
||||||
|
unsafe {
|
||||||
|
drop(Box::from_raw(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
box_free.push(t0.elapsed().as_secs_f64() * 1e9 / MICRO_ALLOC_N as f64);
|
||||||
|
}
|
||||||
|
|
||||||
|
MicroResult {
|
||||||
|
bump_alloc: median(&bump_alloc),
|
||||||
|
bump_free: median(&bump_free),
|
||||||
|
box_alloc: median(&box_alloc),
|
||||||
|
box_free: median(&box_free),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 单用例墙时间:编译一次(计时循环外),预热后每迭代新 VM 执行(与 js
|
||||||
|
/// 压力基准同一 harness),返回当前中位墙毫秒与基线墙毫秒(基线缺失时
|
||||||
|
/// 为 None)。
|
||||||
|
fn run_wall_case(
|
||||||
|
case: &str,
|
||||||
|
config: &BenchConfig,
|
||||||
|
kernel: &Arc<KernelCore>,
|
||||||
|
pool: &Arc<VmPool>,
|
||||||
|
) -> Result<(f64, Option<f64>), String> {
|
||||||
|
let path = format!("tests/stress/{case}.js");
|
||||||
|
let js = std::fs::read_to_string(&path).map_err(|e| format!("读取 {path} 失败: {e}"))?;
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, &js).map_err(|e| format!("解析 {case} 失败: {e:?}"))?;
|
||||||
|
let compiler = Compiler::new();
|
||||||
|
let hash = compiled_module_hash(&program);
|
||||||
|
let module = kernel
|
||||||
|
.code_forge()
|
||||||
|
.get_or_insert_with(hash, || compiler.compile(&program))
|
||||||
|
.map_err(|e| format!("编译 {case} 失败: {e}"))?;
|
||||||
|
|
||||||
|
for _ in 0..config.warmup {
|
||||||
|
let mut guard = pool.spawn();
|
||||||
|
let _ = guard.vm_mut().run(&module);
|
||||||
|
}
|
||||||
|
let mut walls: Vec<f64> = Vec::with_capacity(config.iterations as usize);
|
||||||
|
for _ in 0..config.iterations {
|
||||||
|
let mut guard = pool.spawn();
|
||||||
|
let t0 = Instant::now();
|
||||||
|
let result = guard.vm_mut().run(&module);
|
||||||
|
let wall_ms = t0.elapsed().as_secs_f64() * 1e3;
|
||||||
|
if result.is_err() {
|
||||||
|
return Err(format!("执行 {case} 失败: {result:?}"));
|
||||||
|
}
|
||||||
|
walls.push(wall_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
let baseline = load_baseline().ok().and_then(|b| {
|
||||||
|
b.entries
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.test_name == case)
|
||||||
|
.map(|e| e.wall_time_us as f64 / 1e3)
|
||||||
|
});
|
||||||
|
Ok((median(&walls), baseline))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 中位数:抗单轮尖峰,轮数取奇数。
|
||||||
|
fn median(values: &[f64]) -> f64 {
|
||||||
|
let mut sorted = values.to_vec();
|
||||||
|
sorted.sort_by(f64::total_cmp);
|
||||||
|
sorted[sorted.len() / 2]
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
//! Benchmark 子系统:支持 JS 压力测试(`js`)、Rust 原生 bench(`rust`)
|
//! Benchmark 子系统:支持 JS 压力测试(`js`)、Rust 原生 bench(`rust`)
|
||||||
//! 与内存泄漏检测(`leak`),并附带基线保存/回归对比功能。
|
//! 与内存泄漏检测(`leak`),并附带基线保存/回归对比功能。
|
||||||
|
|
||||||
|
/// 分配成本 go/no-go 测量装置(Plan C 设计门)。
|
||||||
|
pub mod alloc_ab;
|
||||||
/// 基准持久化与回归对比。
|
/// 基准持久化与回归对比。
|
||||||
pub mod baseline;
|
pub mod baseline;
|
||||||
/// JS 压力测试执行。
|
/// JS 压力测试执行。
|
||||||
@@ -45,6 +47,7 @@ pub fn run_benchmarks(config: BenchConfig, kernel: Arc<KernelCore>, pool: Arc<Vm
|
|||||||
Some("object_churn_peak") => leak_detect::run_mem_object_churn_peak(&kernel),
|
Some("object_churn_peak") => leak_detect::run_mem_object_churn_peak(&kernel),
|
||||||
Some("kernel_lifetime") => leak_detect::run_mem_kernel_lifetime(),
|
Some("kernel_lifetime") => leak_detect::run_mem_kernel_lifetime(),
|
||||||
Some("startup_breakdown") => leak_detect::run_mem_startup_breakdown(),
|
Some("startup_breakdown") => leak_detect::run_mem_startup_breakdown(),
|
||||||
|
Some("alloc_ab") => alloc_ab::run_alloc_ab(&config, &kernel, &pool),
|
||||||
_ => leak_detect::run_leak_detect(&config, &kernel, &pool),
|
_ => leak_detect::run_leak_detect(&config, &kernel, &pool),
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
|
|||||||
Reference in New Issue
Block a user