mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
perf: 账目公式切手工口径退役 arena 计数器
This commit is contained in:
@@ -450,17 +450,17 @@ pub fn run_mem_closure_dead_leak(kernel: &Arc<KernelCore>) -> ExitCode {
|
|||||||
report_series("closure_dead_leak", "iter", &series)
|
report_series("closure_dead_leak", "iter", &series)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 校准用例:重源单次 run 后 `full_reset()`,读双 arena 保留锚
|
/// 校准用例:重源单次 run 后 `full_reset()`,读 session 账目锚
|
||||||
/// (`arena_retained_bytes`,reset 边界换新 Bump 后恰 0),再 50 轮轻源
|
/// (`session_bytes_allocated`,全量重置清表归零后恰 0),再 50 轮轻源
|
||||||
/// (每轮 `full_reset()`)采 VmRSS,度量 full_reset 归还路径的跨轮内存
|
/// (每轮 `full_reset()`)采 VmRSS,度量 full_reset 归还路径的跨轮内存
|
||||||
/// 增量(斜率 + 窗口总增量),期望走平。
|
/// 增量(斜率 + 窗口总增量),期望走平。
|
||||||
///
|
///
|
||||||
/// # 注意事项
|
/// # 注意事项
|
||||||
/// 不走 VM 池:drop clean 路径与池内归还走同一 `full_reset`,池锁/condvar
|
/// 不走 VM 池:drop clean 路径与池内归还走同一 `full_reset`,池锁/condvar
|
||||||
/// 噪音与本测面无关。重源瞬时对象全 epoch 分配、不逃逸 global;锚 == 0 为
|
/// 噪音与本测面无关。重源瞬时对象不逃逸 global,全量重置后 session 账目
|
||||||
/// 确定性主门槛(in-engine,免分配器噪音),RSS 序列为次锚(进程级辅助指标,
|
/// 清零;锚 == 0 为确定性主门槛(in-engine,免分配器噪音),RSS 序列为次锚
|
||||||
/// 峰后无新增量——归还 chunk 可能滞留分配器自由链不回 OS,不要求回落
|
/// (进程级辅助指标,峰后无新增量——归还 chunk 可能滞留分配器自由链不回
|
||||||
/// 峰前)。
|
/// OS,不要求回落峰前)。
|
||||||
pub fn run_mem_pool_high_water(kernel: &Arc<KernelCore>) -> ExitCode {
|
pub fn run_mem_pool_high_water(kernel: &Arc<KernelCore>) -> ExitCode {
|
||||||
const LIGHT_ROUNDS: usize = 50;
|
const LIGHT_ROUNDS: usize = 50;
|
||||||
|
|
||||||
@@ -484,7 +484,7 @@ pub fn run_mem_pool_high_water(kernel: &Arc<KernelCore>) -> ExitCode {
|
|||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
}
|
}
|
||||||
vm.full_reset();
|
vm.full_reset();
|
||||||
let anchor = vm.arena_retained_bytes();
|
let anchor = vm.session_bytes_allocated();
|
||||||
eprintln!("[pool_high_water] anchor after full_reset: {anchor} bytes (expect 0)");
|
eprintln!("[pool_high_water] anchor after full_reset: {anchor} bytes (expect 0)");
|
||||||
if anchor != 0 {
|
if anchor != 0 {
|
||||||
return ExitCode::FAILURE;
|
return ExitCode::FAILURE;
|
||||||
@@ -898,6 +898,25 @@ mod tests {
|
|||||||
assert_eq!(r2, 1.0);
|
assert_eq!(r2, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 全量重置后 session 账目锚恰 0:`mem_pool_high_water` 的确定性主门槛
|
||||||
|
/// (锚 == 0)依赖全量重置清表归零,重源 run 后账目非零作对照。
|
||||||
|
#[test]
|
||||||
|
fn session_accounting_zero_after_full_reset() {
|
||||||
|
let kernel = KernelCore::new(KernelConfig::standard());
|
||||||
|
let mut vm = Vm::with_kernel_core(Arc::clone(&kernel));
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = oxide_parser::parse(&allocator, "var o = { a: 1 }; o").expect("parse");
|
||||||
|
let hash = compiled_module_hash(&program);
|
||||||
|
let module = kernel
|
||||||
|
.code_forge()
|
||||||
|
.get_or_insert_with(hash, || Compiler::new().compile(&program))
|
||||||
|
.expect("compile");
|
||||||
|
vm.run(&module).expect("run");
|
||||||
|
assert!(vm.session_bytes_allocated() > 0);
|
||||||
|
vm.full_reset();
|
||||||
|
assert_eq!(vm.session_bytes_allocated(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// 窗口采样器:正斜率序列出判定且斜率还原正确。
|
/// 窗口采样器:正斜率序列出判定且斜率还原正确。
|
||||||
#[test]
|
#[test]
|
||||||
fn sampler_verdict_on_rising_series() {
|
fn sampler_verdict_on_rising_series() {
|
||||||
|
|||||||
@@ -593,7 +593,8 @@ impl SessionGc {
|
|||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - `session_object_ptrs` 仅剩存活对象;`session_bytes_allocated` 重算为
|
/// - `session_object_ptrs` 仅剩存活对象;`session_bytes_allocated` 重算为
|
||||||
/// 存活对象字节(串/BigInt/cell 分量由各清扫路径自行补回);
|
/// 存活对象字节(串分量由串清扫补回;BigInt/cell 不在手工账目内,
|
||||||
|
/// 由 `run_alloc_bytes` 公式按表长单列);
|
||||||
/// - `total_*` 与 `last_collection_*` 统计按对象口径更新(绝对赋值,
|
/// - `total_*` 与 `last_collection_*` 统计按对象口径更新(绝对赋值,
|
||||||
/// 串/BigInt/cell 清扫在其上累加)。
|
/// 串/BigInt/cell 清扫在其上累加)。
|
||||||
///
|
///
|
||||||
@@ -729,15 +730,14 @@ impl SessionGc {
|
|||||||
pub(crate) fn sweep_session_bigints(&mut self, vm: &mut Vm) -> u64 {
|
pub(crate) fn sweep_session_bigints(&mut self, vm: &mut Vm) -> u64 {
|
||||||
let old = vm.gc_state.session_bigint_ptrs.borrow_mut().drain(..).collect::<Vec<_>>();
|
let old = vm.gc_state.session_bigint_ptrs.borrow_mut().drain(..).collect::<Vec<_>>();
|
||||||
let mut freed = 0u64;
|
let mut freed = 0u64;
|
||||||
let mut live_bytes = 0usize;
|
|
||||||
let mut live = Vec::with_capacity(old.len());
|
let mut live = Vec::with_capacity(old.len());
|
||||||
for ptr in old {
|
for ptr in old {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if self.live_bigints.contains(&ptr) {
|
if self.live_bigints.contains(&ptr) {
|
||||||
// 存活——地址不变,无需重写。
|
// 存活——地址不变,无需重写。BigInt 不入手工账目,
|
||||||
live_bytes += size_of::<num_bigint::BigInt>();
|
// 存活字节由 `run_alloc_bytes` 公式按表长单列。
|
||||||
live.push(ptr);
|
live.push(ptr);
|
||||||
} else {
|
} else {
|
||||||
// SAFETY: ptr 在 session_bigint_ptrs 中但不可达,恰好释放一次。
|
// SAFETY: ptr 在 session_bigint_ptrs 中但不可达,恰好释放一次。
|
||||||
@@ -748,7 +748,6 @@ impl SessionGc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*vm.gc_state.session_bigint_ptrs.borrow_mut() = live;
|
*vm.gc_state.session_bigint_ptrs.borrow_mut() = live;
|
||||||
vm.gc_state.session_bytes_allocated = vm.gc_state.session_bytes_allocated.saturating_add(live_bytes);
|
|
||||||
|
|
||||||
self.total_bytes_freed = self.total_bytes_freed.saturating_add(freed);
|
self.total_bytes_freed = self.total_bytes_freed.saturating_add(freed);
|
||||||
self.last_collection_bytes_freed = self.last_collection_bytes_freed.saturating_add(freed);
|
self.last_collection_bytes_freed = self.last_collection_bytes_freed.saturating_add(freed);
|
||||||
@@ -759,8 +758,9 @@ impl SessionGc {
|
|||||||
/// `Box::from_raw` 释放。存活 cell 不被搬移——Box 地址稳定——因此无需
|
/// `Box::from_raw` 释放。存活 cell 不被搬移——Box 地址稳定——因此无需
|
||||||
/// forwarding 表与根指针重写。在 BigInt 清扫之后运行。
|
/// forwarding 表与根指针重写。在 BigInt 清扫之后运行。
|
||||||
///
|
///
|
||||||
/// cell 不在 `session_bytes_allocated` 账目口径内(与字符串/BigInt 补回口径
|
/// cell 不在 `session_bytes_allocated` 手工账目口径内(串分量在账目内,
|
||||||
/// 不同),故只累计释放字节统计、不重算存活账目。
|
/// BigInt/cell 由 `run_alloc_bytes` 公式按表长单列),故只累计释放字节
|
||||||
|
/// 统计、不重算存活账目。
|
||||||
/// 返回释放的字节数。
|
/// 返回释放的字节数。
|
||||||
pub(crate) fn sweep_session_cells(&mut self, vm: &mut Vm) -> u64 {
|
pub(crate) fn sweep_session_cells(&mut self, vm: &mut Vm) -> u64 {
|
||||||
let old = vm.gc_state.session_cell_ptrs.borrow_mut().drain(..).collect::<Vec<_>>();
|
let old = vm.gc_state.session_cell_ptrs.borrow_mut().drain(..).collect::<Vec<_>>();
|
||||||
@@ -881,7 +881,8 @@ impl SessionGc {
|
|||||||
/// 本路径不跑对象 sweep(sweep 内清位不会执行),故开头与结尾都清位——
|
/// 本路径不跑对象 sweep(sweep 内清位不会执行),故开头与结尾都清位——
|
||||||
/// 维持"mark 之后必由清位收尾"的不变量,strings-only 结束不留残留。
|
/// 维持"mark 之后必由清位收尾"的不变量,strings-only 结束不留残留。
|
||||||
/// - 不搬移对象,`session_bytes_allocated` 保持对象账目,手动扣掉字符串账目后
|
/// - 不搬移对象,`session_bytes_allocated` 保持对象账目,手动扣掉字符串账目后
|
||||||
/// 由 `sweep_session_strings` 补回存活串字节,与完整收集的最终账目一致。
|
/// 由 `sweep_session_strings` 补回存活串字节(BigInt/cell 不在手工账目内),
|
||||||
|
/// 与完整收集的最终账目一致。
|
||||||
pub(crate) fn collect_strings_only(&mut self, vm: &mut Vm) {
|
pub(crate) fn collect_strings_only(&mut self, vm: &mut Vm) {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
@@ -967,7 +968,8 @@ impl SessionGc {
|
|||||||
///
|
///
|
||||||
/// # 副作用
|
/// # 副作用
|
||||||
/// - `session_object_ptrs` 仅剩存活,`session_bytes_allocated` 按存活整体
|
/// - `session_object_ptrs` 仅剩存活,`session_bytes_allocated` 按存活整体
|
||||||
/// 重算(对象 + 串 + BigInt;本路径不扫串/BigInt,串 GC 归 strings-only);
|
/// 重算(对象 + 串;本路径不扫串,串表整体计入,串 GC 归 strings-only;
|
||||||
|
/// BigInt/cell 不在手工账目内,由公式按表长单列);
|
||||||
/// - `gc_watermark` = 当前分配包络 + 阈值增量;
|
/// - `gc_watermark` = 当前分配包络 + 阈值增量;
|
||||||
/// - 累计/时长统计更新;mark 位全清(收集后无残留)。
|
/// - 累计/时长统计更新;mark 位全清(收集后无残留)。
|
||||||
pub(crate) fn collect_in_run(&mut self, vm: &mut Vm) {
|
pub(crate) fn collect_in_run(&mut self, vm: &mut Vm) {
|
||||||
@@ -987,8 +989,9 @@ impl SessionGc {
|
|||||||
// 死 cell 随死对象出表:存活 cell 经 mark 种子 + 对象边入存活集,此处清扫。
|
// 死 cell 随死对象出表:存活 cell 经 mark 种子 + 对象边入存活集,此处清扫。
|
||||||
self.sweep_session_cells(vm);
|
self.sweep_session_cells(vm);
|
||||||
|
|
||||||
// 整体口径重算:对象分量已由清扫核心写入,此处补串与 BigInt 分量
|
// 整体口径重算:对象分量已由清扫核心写入,此处补串分量(本路径不扫
|
||||||
// (本路径不扫串/BigInt,按当前表整体计入,与 strings-only 口径一致)。
|
// 串,串表整体计入,与 strings-only 口径一致;BigInt/cell 不在手工
|
||||||
|
// 账目内,由 `run_alloc_bytes` 公式按表长单列)。
|
||||||
let mut string_bytes: usize = 0;
|
let mut string_bytes: usize = 0;
|
||||||
for &ptr in &vm.gc_state.session_string_ptrs {
|
for &ptr in &vm.gc_state.session_string_ptrs {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
@@ -997,12 +1000,7 @@ impl SessionGc {
|
|||||||
// SAFETY: ptr 在字符串表登记,收尾前有效。
|
// SAFETY: ptr 在字符串表登记,收尾前有效。
|
||||||
string_bytes += size_of::<JsString>() + unsafe { (*ptr).payload_bytes() };
|
string_bytes += size_of::<JsString>() + unsafe { (*ptr).payload_bytes() };
|
||||||
}
|
}
|
||||||
let bigint_bytes = vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::<num_bigint::BigInt>();
|
vm.gc_state.session_bytes_allocated = vm.gc_state.session_bytes_allocated.saturating_add(string_bytes);
|
||||||
vm.gc_state.session_bytes_allocated = vm
|
|
||||||
.gc_state
|
|
||||||
.session_bytes_allocated
|
|
||||||
.saturating_add(string_bytes)
|
|
||||||
.saturating_add(bigint_bytes);
|
|
||||||
|
|
||||||
// 抬高下次触发水位:当前分配包络 + 阈值增量——存活包络超阈值时不每指令
|
// 抬高下次触发水位:当前分配包络 + 阈值增量——存活包络超阈值时不每指令
|
||||||
// 重复触发无死对象可回收的白跑。
|
// 重复触发无死对象可回收的白跑。
|
||||||
|
|||||||
@@ -1539,6 +1539,38 @@ fn strings_only_collection_byte_accounting_matches_survivors() {
|
|||||||
assert!(!vm.gc_state.session_string_ptrs.contains(&dead.as_string_ptr_mut()));
|
assert!(!vm.gc_state.session_string_ptrs.contains(&dead.as_string_ptr_mut()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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 s = vm.new_string_owned("formula".repeat(4));
|
||||||
|
vm.regs[1] = s;
|
||||||
|
let b = vm.new_bigint(num_bigint::BigInt::from(7_i128));
|
||||||
|
vm.regs[2] = b;
|
||||||
|
// 未根 cell:收集后不可达,随 sweep 出表。
|
||||||
|
vm.gc_state.alloc_cell(JsValue::int(9), true);
|
||||||
|
|
||||||
|
// 公式 = 手工账目 + BigInt 表长 × 尺寸 + cell 表长 × 尺寸,三分量两两不相交。
|
||||||
|
assert_eq!(
|
||||||
|
vm.run_alloc_bytes(),
|
||||||
|
vm.gc_state.session_bytes_allocated
|
||||||
|
+ vm.gc_state.session_bigint_ptrs.borrow().len() * size_of::<num_bigint::BigInt>()
|
||||||
|
+ vm.gc_state.session_cell_ptrs.borrow().len() * size_of::<Cell>()
|
||||||
|
);
|
||||||
|
// BigInt 不在手工账目内:账目 = 对象(头 + 堆数据,本例无堆区)+ 串。
|
||||||
|
let string_bytes = size_of::<JsString>() + unsafe { (*s.as_string_ptr_mut()).payload_bytes() };
|
||||||
|
assert_eq!(vm.gc_state.session_bytes_allocated, size_of::<JsObject>() + 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::<JsObject>() + string_bytes);
|
||||||
|
// 深采样全量重算 = 轻层公式 + 逐对象堆数据重算(本例无堆区,两值相等)。
|
||||||
|
assert_eq!(vm.run_alloc_bytes_full(), vm.run_alloc_bytes() as u64);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runtime_collection_below_threshold_is_noop() {
|
fn runtime_collection_below_threshold_is_noop() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl Vm {
|
|||||||
if bytes > self.gc_state.session_bytes_peak {
|
if bytes > self.gc_state.session_bytes_peak {
|
||||||
self.gc_state.session_bytes_peak = bytes;
|
self.gc_state.session_bytes_peak = bytes;
|
||||||
}
|
}
|
||||||
// 单 run 分配包络高水位:O(1) 三计数器读(双 arena + session 账目)。
|
// 单 run 分配包络高水位:O(1) 读(手工账目 + BigInt/cell 表长)。
|
||||||
let alloc = self.run_alloc_bytes();
|
let alloc = self.run_alloc_bytes();
|
||||||
if alloc > self.gc_state.run_alloc_peak {
|
if alloc > self.gc_state.run_alloc_peak {
|
||||||
self.gc_state.run_alloc_peak = alloc;
|
self.gc_state.run_alloc_peak = alloc;
|
||||||
|
|||||||
@@ -275,45 +275,34 @@ impl Vm {
|
|||||||
self.gc_state.session_bytes_peak
|
self.gc_state.session_bytes_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 双 arena(epoch + session 对象)当前 chunk 已分配字节之和。
|
|
||||||
///
|
|
||||||
/// # 注意事项
|
|
||||||
/// reset 边界读 = 换新 Bump 后空 arena 恰 0(空 chunk 哨兵计 0);
|
|
||||||
/// run 中途读 = 当前 chunk 用量(非全 arena 口径——run 计量走
|
|
||||||
/// [`Self::run_alloc_bytes`],勿据此断言)。
|
|
||||||
pub fn arena_retained_bytes(&self) -> usize {
|
|
||||||
self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 本 run 累计分配字节的高水位:`run_alloc_bytes` 的顶层指令边界
|
/// 本 run 累计分配字节的高水位:`run_alloc_bytes` 的顶层指令边界
|
||||||
/// 采样上界,run 边界(reset/full_reset)重起算。留存内存观测锚。
|
/// 采样上界,run 边界(reset/full_reset)重起算。留存内存观测锚。
|
||||||
pub fn run_alloc_peak(&self) -> usize {
|
pub fn run_alloc_peak(&self) -> usize {
|
||||||
self.gc_state.run_alloc_peak
|
self.gc_state.run_alloc_peak
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 本 run 累计分配字节:epoch arena + session 对象 arena + session 手工堆
|
/// 本 run 累计分配字节:session 手工堆账目(session 对象及其属性向量 +
|
||||||
/// 账目(session 串 + session 对象及其属性向量 + GC 后补回的 BigInt)。
|
/// session 串)+ BigInt 表长 × `size_of::<BigInt>` + cell 表长 ×
|
||||||
/// 单次 run 内单调不减(执行期对象不回收、
|
/// `size_of::<Cell>`。三分量两两不相交(BigInt 与 cell 不在手工账目内),
|
||||||
/// 串 GC 只降手工堆账目而 arena 不减);run 边界(reset)后重新起算,
|
/// O(1) 读,供单 run 分配上限判定。收集释放后读数回落存活集量级;
|
||||||
/// 供单 run 分配上限判定。
|
/// run 边界(reset/full_reset)后账目与表清零重起算。
|
||||||
///
|
///
|
||||||
/// 注意:手工堆账目只在 promote/字符串分配/GC 回收点更新,执行期对象
|
/// 注意:手工堆账目只在对象分配/字符串分配/GC 回收点更新,执行期对象
|
||||||
/// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合
|
/// 属性区(元素/属性向量扩容)增长对其不可见——上限判定须配合
|
||||||
/// [`Self::run_alloc_bytes_full`] 的深采样层。
|
/// [`Self::run_alloc_bytes_full`] 的深采样层。
|
||||||
pub(crate) fn run_alloc_bytes(&self) -> usize {
|
pub(crate) fn run_alloc_bytes(&self) -> usize {
|
||||||
self.epoch.bump().allocated_bytes()
|
self.gc_state.session_bytes_allocated
|
||||||
+ self.gc_state.session_epoch.allocated_bytes()
|
+ self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()
|
||||||
+ self.gc_state.session_bytes_allocated
|
+ self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 本 run 累计分配字节的全量重算版:base 只取两个 arena 计数器
|
/// 本 run 累计分配字节的全量重算版:base 与 [`Self::run_alloc_bytes`] 同
|
||||||
/// (epoch + session 对象 arena),手工堆逐一重算——已登记对象的堆数据
|
/// 一口径(手工账目 + BigInt/cell 表长),其上逐一重算已登记对象的堆数据
|
||||||
/// (属性/元素向量 + upvalue 列表 + native 状态盒)、session 串、BigInt
|
/// (属性/元素向量 + upvalue 列表 + native 状态盒)容量——重算即属性区
|
||||||
/// 与 upvalue cell 的容量。三分量(arena / 对象堆数据 / 串-BigInt-cell)
|
/// 扩容等账目盲区的兜底。base 的对象分量是最近分配/回收点的账目快照,
|
||||||
/// 两两不相交,且不含 session 手工堆账目——无交叠不双计,重算即属性区
|
/// 重算取当前容量,读数偏高,上限判定偏安全侧。
|
||||||
/// 扩容等账目盲区的兜底。
|
|
||||||
pub(crate) fn run_alloc_bytes_full(&self) -> u64 {
|
pub(crate) fn run_alloc_bytes_full(&self) -> u64 {
|
||||||
let mut bytes = (self.epoch.bump().allocated_bytes() + self.gc_state.session_epoch.allocated_bytes()) as u64;
|
let mut bytes = self.run_alloc_bytes() as u64;
|
||||||
for &ptr in self.gc_state.session_object_ptrs.iter() {
|
for &ptr in self.gc_state.session_object_ptrs.iter() {
|
||||||
if ptr.is_null() {
|
if ptr.is_null() {
|
||||||
continue;
|
continue;
|
||||||
@@ -321,12 +310,6 @@ impl Vm {
|
|||||||
// SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。
|
// SAFETY: ptr 来自对象表登记,dispatch 安全点处仍有效。
|
||||||
bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr });
|
bytes += SessionGc::object_heap_data_bytes(unsafe { &*ptr });
|
||||||
}
|
}
|
||||||
for &ptr in &self.gc_state.session_string_ptrs {
|
|
||||||
// SAFETY: ptr 来自字符串表登记,收尾前始终有效。
|
|
||||||
bytes += (std::mem::size_of::<oxide_types::object::JsString>() + unsafe { (*ptr).payload_bytes() }) as u64;
|
|
||||||
}
|
|
||||||
bytes += (self.gc_state.session_bigint_ptrs.borrow().len() * std::mem::size_of::<num_bigint::BigInt>()) as u64;
|
|
||||||
bytes += (self.gc_state.session_cell_ptrs.borrow().len() * std::mem::size_of::<Cell>()) as u64;
|
|
||||||
bytes
|
bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ pub(crate) struct GcState {
|
|||||||
pub(crate) session_gc: SessionGc,
|
pub(crate) session_gc: SessionGc,
|
||||||
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
|
pub(crate) session_object_ptrs: Vec<*mut JsObject>,
|
||||||
pub(crate) session_string_ptrs: Vec<*mut JsString>,
|
pub(crate) session_string_ptrs: Vec<*mut JsString>,
|
||||||
/// BigInt 堆 box(`Box<i128>`)追踪表。`RefCell` 使 `&self` 的
|
/// BigInt 堆 box 追踪表。`RefCell` 使 `&self` 的分配入口(常量池
|
||||||
/// `convert_immutables` 也能登记新 box。与字符串不同,BigInt 不参与
|
/// `convert_immutables`)也能登记新 box。BigInt 参与 mark/sweep 回收
|
||||||
/// mark/sweep 回收(值量少),只在 full_reset 统一释放。
|
/// (死 BigInt 随收集释放,存活字节由 `run_alloc_bytes` 公式按表长
|
||||||
|
/// 单列),full_reset 为收尾兜底(表已空时 no-op)。
|
||||||
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut num_bigint::BigInt>>,
|
pub(crate) session_bigint_ptrs: RefCell<Vec<*mut num_bigint::BigInt>>,
|
||||||
/// upvalue cell(`Box<Cell>`)追踪表。`RefCell` 使 `&self` 的分配入口也能
|
/// upvalue cell(`Box<Cell>`)追踪表。`RefCell` 使 `&self` 的分配入口也能
|
||||||
/// 登记新 box。cell 独立堆分配、地址稳定,不参与对象搬移(原地 sweep
|
/// 登记新 box。cell 独立堆分配、地址稳定,不参与对象搬移(原地 sweep
|
||||||
|
|||||||
@@ -449,8 +449,8 @@ fn reset_drops_stale_uncaught_value() {
|
|||||||
assert!(vm.last_uncaught_value.is_none(), "full_reset 应清空未捕获异常侧通道");
|
assert!(vm.last_uncaught_value.is_none(), "full_reset 应清空未捕获异常侧通道");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run 边界换新 Bump 后双 arena 保留锚恰 0:重源 run 冲高水位,
|
/// 全量重置后 epoch/session 两 arena 均空:容量不跨 reset 保留。对象
|
||||||
/// full_reset 后 epoch/session 两 arena 均空(容量不跨 reset 保留)。
|
/// Box 化后 arena 零分配,此不变量是分配包络公式排除 arena 计数器的前提。
|
||||||
#[test]
|
#[test]
|
||||||
fn full_reset_zeroes_arena_retained() {
|
fn full_reset_zeroes_arena_retained() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user