From 3c6310c58607a2d740f5032da06d9f56fcad5f6a Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 1 Oct 2026 14:01:33 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=B7=B1=E5=B1=82=E5=88=86=E9=85=8D?= =?UTF-8?q?=E5=85=AC=E5=BC=8F=E6=94=B9=E5=85=A8=E9=87=8F=E9=87=8D=E7=AE=97?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E5=AF=B9=E8=B1=A1=E5=A0=86=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=8F=8C=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 深层采样层不再以手工账目为 base 叠加逐对象堆数据重算,改为对象、串、 BigInt 表长、cell 表长四分量各按当前值计一次、互不相交,消除存活对象 堆数据整份双计;bench 留存说明的账目成分列举与 run 边界 doc 措辞同步 改现语义,公式单测夹具补带元素向量的对象,钉深层与轻层零增长逐位相等。 --- crates/oxide_cli/src/bench/output.rs | 11 +++++----- crates/oxide_vm/src/session_gc/tests.rs | 26 +++++++++++++++++++---- crates/oxide_vm/src/vm/gc_hooks.rs | 28 +++++++++++++++++-------- 3 files changed, 47 insertions(+), 18 deletions(-) diff --git a/crates/oxide_cli/src/bench/output.rs b/crates/oxide_cli/src/bench/output.rs index e13ba1cf..678dabc8 100644 --- a/crates/oxide_cli/src/bench/output.rs +++ b/crates/oxide_cli/src/bench/output.rs @@ -15,11 +15,12 @@ const MEMORY_BENCH_NOTES: &str = "\n内存基准用例(mem_*):\n mem_map_retain 原生盒边持有:Map 持 20000 组字符串键 + 对象值 mem_set_retain 原生盒边持有:Set 持 20000 个对象(各带 payload 字符串) mem_string_rope 拼接 rope 链留存:重复二元拼接形成深 Cons 链(账目按逻辑长度计,数值高于实际足迹) -口径:Peak B/Ret B 为 session 堆账目(对象头 + 堆数据 + 存活串/BigInt);epoch arena -的临时驻留不计入字节账目(数量见 JSON 的 epoch_objects)。Peak B 为顶层指令 -边界采样的 session arena 高水位;单指令内瞬态与 epoch 高水位不计。Ret B 为 workload 后 -晋升 + 完整 GC 的真存活集,至多含一份引擎内部副本:逃逸屏障留 epoch 原件且顶层 -寄存器根不回写时,测量时晋升会把原件再克隆一份(object_graph 见留存锚测试)。 +口径:Peak B/Ret B 为 session 堆账目(对象头 + 堆数据 + 存活串);BigInt 不入 +该账目,由单 run 分配公式按表长单列。epoch arena 的临时驻留不计入字节账目 +(数量列恒零)。Peak B 为顶层指令边界采样的 session 堆账目高水位;单指令内瞬态 +与 epoch 高水位不计。Ret B 为 workload 后晋升 + 完整 GC 的真存活集,至多含一份 +引擎内部副本:逃逸屏障留 epoch 原件且顶层寄存器根不回写时,测量时晋升会把原件 +再克隆一份(object_graph 见留存锚测试)。 "; /// 把结果格式化为对齐的文本表格(供终端打印)。 diff --git a/crates/oxide_vm/src/session_gc/tests.rs b/crates/oxide_vm/src/session_gc/tests.rs index 1f78130e..c232333a 100644 --- a/crates/oxide_vm/src/session_gc/tests.rs +++ b/crates/oxide_vm/src/session_gc/tests.rs @@ -1544,6 +1544,15 @@ fn run_alloc_formula_lists_bigint_and_cell_via_tables() { let mut vm = Vm::new(); let obj = plain_object(&mut vm); vm.regs[0] = JsValue::from_js_object(obj); + // 带堆区对象:元素向量非空(账目含头 + 向量容量),深层等式在双计形态 + // 下不成立,钉向量面互不相交。 + let arr = vm.alloc_object(JsObject::new_array( + oxide_kernel::shape_forge::EMPTY_SHAPE_ID, + JsValue::from_js_object(vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject), + 2, + vm.epoch.bump(), + )); + vm.regs[3] = JsValue::from_js_object(arr); let s = vm.new_string_owned("formula".repeat(4)); vm.regs[1] = s; let b = vm.new_bigint(num_bigint::BigInt::from(7_i128)); @@ -1558,16 +1567,25 @@ fn run_alloc_formula_lists_bigint_and_cell_via_tables() { + vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::() + vm.gc_state.session_cell_ptrs.borrow().len() * size_of::() ); - // BigInt 不在手工账目内:账目 = 对象(头 + 堆数据,本例无堆区)+ 串。 + // BigInt 不在手工账目内:账目 = 两对象(头 + 堆数据)+ 串,数组带元素向量。 let string_bytes = size_of::() + unsafe { (*s.as_string_ptr_mut()).payload_bytes() }; - assert_eq!(vm.gc_state.session_bytes_allocated, size_of::() + string_bytes); + let arr_heap_bytes = SessionGc::object_heap_data_bytes(unsafe { &*arr }); + assert!(arr_heap_bytes > 0, "数组元素向量应计入对象堆数据"); + assert_eq!( + vm.gc_state.session_bytes_allocated, + 2 * size_of::() + arr_heap_bytes as usize + string_bytes + ); // 完整收集后:死 cell 出表、存活 BigInt 留表,账目保持对象 + 串口径。 vm.collect_session_gc(); assert_eq!(vm.gc_state.session_cell_ptrs.borrow().len(), 0); assert_eq!(vm.gc_state.session_bigint_ptrs.borrow().len(), 1); - assert_eq!(vm.gc_state.session_bytes_allocated, size_of::() + string_bytes); - // 深采样全量重算 = 轻层公式 + 逐对象堆数据重算(本例无堆区,两值相等)。 + assert_eq!( + vm.gc_state.session_bytes_allocated, + 2 * size_of::() + arr_heap_bytes as usize + string_bytes + ); + // 深采样全量重算与轻层公式零增长下逐位相等:存活对象带堆区(元素向量), + // 双计形态下深层严格大于轻层,该断言即失守。 assert_eq!(vm.run_alloc_bytes_full(), vm.run_alloc_bytes() as u64); } diff --git a/crates/oxide_vm/src/vm/gc_hooks.rs b/crates/oxide_vm/src/vm/gc_hooks.rs index 7bc9c59e..d64a38b6 100644 --- a/crates/oxide_vm/src/vm/gc_hooks.rs +++ b/crates/oxide_vm/src/vm/gc_hooks.rs @@ -1,7 +1,7 @@ //! GC 根遍历与执行期收集钩子:根统一枚举、搬移后指针重写、执行期两档 //! 收集入口(安全点门控)与 session GC 账目统计访问器。 -use oxide_types::object::{Cell, JsObject}; +use oxide_types::object::{Cell, JsObject, JsString}; use oxide_types::value::JsValue; use super::frames::Completion; @@ -285,7 +285,7 @@ impl Vm { /// session 串)+ BigInt 表长 × `size_of::` + cell 表长 × /// `size_of::`。三分量两两不相交(BigInt 与 cell 不在手工账目内), /// O(1) 读,供单 run 分配上限判定。收集释放后读数回落存活集量级; - /// run 边界(reset/full_reset)后账目与表清零重起算。 + /// 轻量 reset 保留账目与表(仅复位峰值与水位),full_reset 清零重起算。 /// /// 注意:手工堆账目只在对象分配/字符串分配/GC 回收点更新,执行期对象 /// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合 @@ -296,20 +296,30 @@ impl Vm { + self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::() } - /// 本 run 累计分配字节的全量重算版:base 与 [`Self::run_alloc_bytes`] 同 - /// 一口径(手工账目 + BigInt/cell 表长),其上逐一重算已登记对象的堆数据 - /// (属性/元素向量 + upvalue 列表 + native 状态盒)容量——重算即属性区 - /// 扩容等账目盲区的兜底。base 的对象分量是最近分配/回收点的账目快照, - /// 重算取当前容量,读数偏高,上限判定偏安全侧。 + /// 本 run 累计分配字节的全量重算版:各分量按当前值各计一次、互不相交—— + /// 存活对象头加堆数据(属性/元素/meta 向量容量、upvalue 列表容量、native + /// 状态盒)+ 存活串(头加 payload)+ BigInt 表长 × 尺寸 + cell 表长 × + /// 尺寸。重算即属性区扩容等账目盲区的兜底,读数为当前真实总量,零增长时 + /// 与 [`Self::run_alloc_bytes`] 的轻层公式逐位相等。 pub(crate) fn run_alloc_bytes_full(&self) -> u64 { - let mut bytes = self.run_alloc_bytes() as u64; + let mut bytes = 0u64; for &ptr in self.gc_state.session_object_ptrs.iter() { if ptr.is_null() { continue; } // SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。 - bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr }); + let obj = unsafe { &*ptr }; + bytes += std::mem::size_of::() as u64 + SessionGc::object_heap_data_bytes(obj); } + for &ptr in &self.gc_state.session_string_ptrs { + if ptr.is_null() { + continue; + } + // SAFETY: ptr 在字符串表登记,收尾前有效。 + bytes += (std::mem::size_of::() + unsafe { (*ptr).payload_bytes() }) as u64; + } + bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::()) as u64; + bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::()) as u64; bytes }