bench: 主判改确定性列零容差并重锚全谱基线

This commit is contained in:
2026-10-04 15:17:20 +00:00
parent 7eb5d111d6
commit 1b81c09cd6
7 changed files with 669 additions and 611 deletions
+25 -9
View File
@@ -12,7 +12,10 @@ impl Baseline {
}
}
/// 一次性能回退:当前指标相对基线的比值超过容差即视为回归。
/// 一次性能回退信号:当前指标相对基线的比值超过容差即报出。
///
/// `informational` 标记信息性信号(墙时型列):随表格打印供人工参考,
/// 不影响退出码;非信息性信号是回归门,出现即退出码 1。
pub struct Regression {
pub metric: String,
pub test_name: String,
@@ -20,6 +23,7 @@ pub struct Regression {
pub current: f64,
pub ratio: f64,
pub tolerance: f64,
pub informational: bool,
}
/// 从 `benchmark_baseline.json` 加载基线;文件不存在时返回空基线。
@@ -47,8 +51,13 @@ pub fn save_baseline(results: &[MetricCollection]) -> Result<(), String> {
Ok(())
}
/// 对比当前结果与基线,返回所有超过各自容差(wall_time 10%、GC 类 50%、其它 20%)的回归项。
/// 方向语义:`ic_hit_rate` 越高越好(下降超容差才报回归),其余指标越低越好(上升超容差报回归)。
/// 对比当前结果与基线,返回所有超过各自容差的信号项。
///
/// 列语义分三档:确定性列(指令数、内存账目、垃圾回收计数、内联缓存计数)
/// 跨运行逐字节一致,零容差,双向任何变化即信号;墙时型列(wall、exec、
/// compile、gc_collection)容差 0.50,信息性,只打印不进退出码;
/// `ic_hit_rate` 越高越好(下降超容差才报),其余列越低越好(上升超容差报)。
/// 两侧零值跳过(基线为 0、当前非零的新分配场景由重锚吸收,不在此报)。
pub fn compare_baseline(current: &[MetricCollection]) -> Vec<Regression> {
let baseline = match load_baseline() {
Ok(b) => b,
@@ -77,14 +86,20 @@ pub fn compare_baseline(current: &[MetricCollection]) -> Vec<Regression> {
if base_val == 0.0 {
continue;
}
let tolerance = match name {
"wall_time_us" => 0.10,
"gc_trigger_count" | "gc_bytes_freed" | "gc_objects_scanned" | "gc_collection_us" => 0.50,
"session_bytes" | "peak_bytes" | "retained_bytes" | "retained_objects" => 0.20,
_ => 0.20,
// 确定性列零容差(双向任何变化即信号);墙时型列信息性;
// ic_hit_rate 越高越好;其余列保留 20% 默认带。
let (tolerance, informational) = match name {
"instruction_count" | "session_objects" | "session_bytes" | "gc_trigger_count"
| "gc_bytes_freed" | "gc_objects_scanned" | "ic_hits" | "ic_misses" | "peak_bytes"
| "retained_bytes" | "retained_objects" => (0.0, false),
"wall_time_us" | "exec_time_us" | "compile_time_us" | "gc_collection_us" => (0.50, true),
"ic_hit_rate" => (0.05, false),
_ => (0.20, false),
};
let ratio = cur_val / base_val;
let regressed = if matches!(name, "ic_hit_rate") {
let regressed = if tolerance == 0.0 {
(ratio - 1.0).abs() > 1e-9
} else if matches!(name, "ic_hit_rate") {
ratio < 1.0 - tolerance
} else {
ratio > 1.0 + tolerance
@@ -97,6 +112,7 @@ pub fn compare_baseline(current: &[MetricCollection]) -> Vec<Regression> {
current: cur_val,
ratio,
tolerance,
informational,
});
}
}
+20 -2
View File
@@ -155,9 +155,27 @@ pub fn run_js_stress_bench(config: &BenchConfig, kernel: &Arc<KernelCore>, pool:
eprintln!("Baseline saved to BENCHMARK_BASELINE.md + benchmark_baseline.json");
} else {
let regressions = compare_baseline(&results);
if !regressions.is_empty() {
// 信息性旗标(墙时型列)只打印供人工参考,不影响退出码。
let informational: Vec<_> = regressions.iter().filter(|r| r.informational).collect();
if !informational.is_empty() {
eprintln!("\nWall-time drift (informational, does not affect exit code):");
for r in &informational {
eprintln!(
" {}: {} baseline={} current={} ratio={:.2}% tolerance={:.0}%",
r.test_name,
r.metric,
r.baseline,
r.current,
(r.ratio - 1.0) * 100.0,
r.tolerance * 100.0,
);
}
}
// 回归门:确定性列等硬信号出现即退出码 1。
let hard: Vec<_> = regressions.iter().filter(|r| !r.informational).collect();
if !hard.is_empty() {
eprintln!("\nRegression detected:");
for r in &regressions {
for r in &hard {
eprintln!(
" {}: {} baseline={} current={} ratio={:.2}% tolerance={:.0}%",
r.test_name,
+12
View File
@@ -23,6 +23,17 @@ const MEMORY_BENCH_NOTES: &str = "\n内存基准用例(mem_*):\n
再克隆一份(object_graph 见留存锚测试)。
";
/// A/B 判据口径说明,随表格尾部输出。
const BENCH_JUDGMENT_NOTES: &str = "\nA/B 判据口径:\n
主判(回归门):确定性列——指令数、内存账目(session/peak/retained)、
垃圾回收计数、内联缓存计数——跨运行逐字节一致,零容差,双向任何变化即信号;
基线对比的退出码 1 只认这些列。
次判(墙时间):wall/exec/compile/gc_collection 列在 ±5% 带内(固定核、
warmup 2、三轮中位);基线对比容差 0.50,信息性,不影响退出码。
基线文件是陈旧锚(安静树单次快照),不是 A/B 仪器;A/B 是同机同宿主
严格交错对比。
";
/// 把结果格式化为对齐的文本表格(供终端打印)。
///
/// `Sess B`/`Peak B`/`Ret B` 为 session 堆账目(字节):执行期累计 / 峰值高水位 /
@@ -64,6 +75,7 @@ pub fn format_text_table(results: &[MetricCollection]) -> String {
out.push('\n');
}
out.push_str(MEMORY_BENCH_NOTES);
out.push_str(BENCH_JUDGMENT_NOTES);
out
}