perf: IC 多态化 4 槽缓存(FIFO 滚动写回)消除多态场景 miss 重解析
每条 IC 指令扩展字 3→8(4 槽 × [shape_id|proto_depth, slot]),命中槽 0 单态快路径零额外成本,槽 1..3 懒解码遍历,miss 写回 FIFO 滚动(新条目进 槽 0、最老丢弃)。修复原型链 IC 深度解析多走一步与 prop_forge 模板不匹配 不写回两个使缓存恒失效的问题。ic_cache 基准命中率 34.8%→78.9%, 耗时 -29%;单态 prop_access 因扩展字布局膨胀有 ~20% 取指回归(后续 side-table 方案可根除)。
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
//! IC 多态缓存(4 槽,FIFO 滚动写回)行为测试。
|
||||
//!
|
||||
//! `run()` 每次从 CodeForge 原始 bytecode 冷启动(IC 学习不跨 run 持久),
|
||||
//! 因此全部断言基于**单次 run 内**的 miss 计数与迭代数的对比:多态 4 槽
|
||||
//! 覆盖的 shape 只产生"每 shape 一次"的学习 miss,超出槽容量的轮换则
|
||||
//! 每次访问都 miss(FIFO 逐访问滚动)。
|
||||
|
||||
use oxide_compiler::compiler::Compiler;
|
||||
use oxide_parser::Allocator;
|
||||
use oxide_vm::vm::Vm;
|
||||
|
||||
/// 编译并单次运行,返回 (结果, hits, misses)。
|
||||
fn run_once(source: &str) -> (String, u64, u64) {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(&allocator, source).expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let mut vm = Vm::new();
|
||||
let r = vm.run(&module).expect("run").to_string();
|
||||
(r, vm.ic_hit_count(), vm.ic_miss_count())
|
||||
}
|
||||
|
||||
/// 4 种 shape 轮换访问同一 `.x` 站点:每 shape 一次学习 miss,之后全命中。
|
||||
#[test]
|
||||
fn poly4_shape_rotation_only_learning_misses() {
|
||||
let (r, hits, misses) = run_once(
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 }, c = { x: 4, z: 5 }, d = { x: 6, y: 7, z: 8 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var t = [a, b, c, d][i % 4];
|
||||
sum += t.x;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "32500", "x 值 1/2/4/6 各 2500 次累加");
|
||||
assert_eq!(misses, 4, "4 shape 均被 4 槽缓存,仅首轮学习各 1 次 miss");
|
||||
assert!(hits >= 9990, "后续访问全部命中(实际命中 {hits})");
|
||||
}
|
||||
|
||||
/// 第 5 种 shape 挤掉 FIFO 最老槽:顺序轮换下每访问都 miss(缓存容量不足)。
|
||||
#[test]
|
||||
fn poly5_shape_rotation_evicts_oldest() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var s = [
|
||||
{ x: 1 }, { x: 2, y: 3 }, { x: 4, z: 5 }, { x: 6, y: 7, z: 8 }, { x: 9, a: 1, b: 2 }
|
||||
];
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
sum += s[i % 5].x;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "44000", "x 值 1/2/4/6/9 各 2000 次累加");
|
||||
assert!(
|
||||
misses > 5000,
|
||||
"5 shape 顺序轮换超出 4 槽容量,FIFO 逐访问滚动导致 miss 过半(实际 {misses})"
|
||||
);
|
||||
}
|
||||
|
||||
/// 单态连续访问:仅 1 次学习 miss,空槽 break 不越界。
|
||||
#[test]
|
||||
fn monomorphic_only_one_learning_miss() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var a = { x: 1 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) { sum += a.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "10000");
|
||||
assert_eq!(misses, 1, "单态站点仅首轮学习 miss");
|
||||
}
|
||||
|
||||
/// 空 IC(未学习)走慢路径并写回槽 0;首次访问即正确。
|
||||
#[test]
|
||||
fn cold_cache_first_access_resolves() {
|
||||
let (r, hits, misses) = run_once(
|
||||
r#"var a = { x: 7 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 1000; i++) { sum += a.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "7000");
|
||||
assert_eq!(misses, 1);
|
||||
assert_eq!(hits, 999, "学习后 999 次全命中");
|
||||
}
|
||||
|
||||
/// accessor 属性走 has_prop_meta 慢路径,不经 IC 缓存;语义正确即可。
|
||||
#[test]
|
||||
fn accessor_property_semantics_preserved() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var o = { get x() { return 1; } };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 100; i++) { sum += o.x; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "100", "getter 每次返回 1");
|
||||
}
|
||||
|
||||
/// `__proto__` 赋值走原型设置路径,不写 IC;原型属性读语义正确。
|
||||
#[test]
|
||||
fn proto_assignment_not_cached_semantics_preserved() {
|
||||
let (r, _hits, _misses) = run_once(
|
||||
r#"var p = { m: 42 };
|
||||
var o = {};
|
||||
o.__proto__ = p;
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 100; i++) { sum += o.m; }
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "4200", "__proto__ 赋值建立原型链后经 IC 原型路径读到 42");
|
||||
}
|
||||
|
||||
/// 原型链 depth>0 多态:两个不同 proto shape 轮换,各槽独立 depth。
|
||||
#[test]
|
||||
fn proto_polymorphic_two_protos_learning_misses() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var b1 = { v: 1 }, b2 = { v: 2, w: 3 };
|
||||
var o1 = Object.create(b1), o2 = Object.create(b2);
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var o = (i % 2 === 0) ? o1 : o2;
|
||||
sum += o.v;
|
||||
}
|
||||
sum"#,
|
||||
);
|
||||
assert_eq!(r, "15000");
|
||||
assert_eq!(misses, 2, "两个 proto shape 各学习一次,后续 depth>0 命中");
|
||||
}
|
||||
|
||||
/// `rerun()` 清空全部 IC 扩展字:清除后访问重新学习(对照 regression_rerun_clears_ic_cache)。
|
||||
#[test]
|
||||
fn rerun_clears_polymorphic_slots() {
|
||||
let allocator = Allocator::default();
|
||||
let program = oxide_parser::parse(
|
||||
&allocator,
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 };
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var t = (i % 2 === 0) ? a : b;
|
||||
sum += t.x;
|
||||
}
|
||||
sum"#,
|
||||
)
|
||||
.expect("parse");
|
||||
let module = Compiler::new().compile(&program).expect("compile");
|
||||
let mut vm = Vm::new();
|
||||
vm.run(&module).expect("run1");
|
||||
let learned = vm.ic_miss_count();
|
||||
// run() 每次冷启动,此处直接验证 rerun 的可重入性:清除后仍能正常执行。
|
||||
assert_eq!(format!("{}", vm.rerun().expect("rerun")), "1500");
|
||||
let after_rerun = vm.ic_miss_count();
|
||||
assert!(
|
||||
after_rerun >= learned,
|
||||
"rerun 清零多态槽后重新学习(miss 不降反增,实际 {after_rerun} ≥ {learned})"
|
||||
);
|
||||
}
|
||||
|
||||
/// member 读侧(obj.x++)多 shape 轮换:read_member_prop 多态命中,
|
||||
/// 写侧(set_member_prop)暂不接命中——本任务只验证格式同步不错位。
|
||||
#[test]
|
||||
fn member_compound_polymorphic_read_side_hits() {
|
||||
let (r, _hits, misses) = run_once(
|
||||
r#"var a = { x: 1 }, b = { x: 2, y: 3 };
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
var t = (i % 2 === 0) ? a : b;
|
||||
t.x += 1;
|
||||
}
|
||||
a.x + b.x"#,
|
||||
);
|
||||
assert_eq!(r, "10003", "两对象 x 各自增 5000 次(从 1/2 起)");
|
||||
assert!(misses < 5000, "读侧 4 槽缓存使 miss 远低于迭代数(实际 {misses},写侧新属性路径不计入)");
|
||||
}
|
||||
Reference in New Issue
Block a user