perf: 数组元素独立存储区,push 改为摊销 O(1) 尾插

This commit is contained in:
2026-08-10 16:01:09 +08:00
parent 7efb26bc96
commit d8952a59fa
6 changed files with 391 additions and 63 deletions
+36
View File
@@ -198,6 +198,42 @@ mod tests {
assert!(!is_epoch_object(&vm, meta.set));
}
#[test]
fn session_arena_promotes_array_elements_and_element_meta() {
let mut vm = Vm::new();
let elem_child = plain_object(&mut vm);
let meta_getter = plain_object(&mut vm);
let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch.alloc(JsObject::new_array(
EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
2,
vm.epoch.bump(),
));
unsafe {
(*arr).set_prop_at(0, JsValue::from_js_object(elem_child));
(*arr).set_accessor_meta(
1,
JsValue::from_js_object(meta_getter),
JsValue::undefined(),
PropAttributes::DEFAULT_DATA,
);
(*arr).set_prop_shape(0, JsValue::int(99));
}
let promoted = vm.promote_object(arr);
let promoted_arr = unsafe { &*promoted };
assert!(promoted_arr.is_session_epoch());
// 数组元素(含访问器 get)与命名属性随 clone + rewrite 迁移到 session。
assert!(!is_epoch_object(&vm, promoted_arr.get_prop_at(0)));
let meta = promoted_arr.prop_meta_at(1).expect("element accessor meta");
assert!(!is_epoch_object(&vm, meta.get));
assert_eq!(promoted_arr.get_prop_shape(0), JsValue::int(99));
// 元素计数与长度不因 promotion 改变。
assert_eq!(promoted_arr.prop_count(), 2);
}
#[test]
fn session_arena_barrier_promotes_global_root_write() {
let mut vm = Vm::new();
+68
View File
@@ -55,6 +55,19 @@ impl SessionGc {
fn object_edges(obj: &JsObject) -> Vec<JsValue> {
let mut edges = Vec::new();
if let Some(elements) = obj.array_elements_vec() {
edges.extend(elements.iter().copied().filter(|val| val.is_object()));
}
if let Some(meta) = obj.array_elements_meta_vec() {
for entry in meta.iter().flatten() {
if entry.get.is_object() {
edges.push(entry.get);
}
if entry.set.is_object() {
edges.push(entry.set);
}
}
}
if let Some(props) = obj.hash_props_vec() {
edges.extend(props.iter().copied().filter(|val| val.is_object()));
}
@@ -119,6 +132,13 @@ impl SessionGc {
/// 无害地记录;sweep 只遍历 `session_string_ptrs`,`live` 中的非 session 指针
/// 永远不会被查询。
fn record_object_string_edges(live: &mut HashSet<*mut JsString, FxBuildHasher>, obj: &JsObject) {
if let Some(elements) = obj.array_elements_vec() {
for value in elements.iter() {
if value.is_string() {
live.insert(value.as_string_ptr_mut());
}
}
}
if let Some(props) = obj.hash_props_vec() {
for value in props.iter() {
if value.is_string() {
@@ -266,6 +286,21 @@ impl SessionGc {
}
let mut freed_bytes = 0u64;
let elems_ptr = obj.array_elements_raw() as *mut Vec<JsValue>;
if !elems_ptr.is_null() {
let vec = Box::from_raw(elems_ptr);
freed_bytes += size_of::<Vec<JsValue>>() as u64 + (vec.capacity() * size_of::<JsValue>()) as u64;
std::mem::drop(vec);
}
let elems_meta_ptr = obj.array_elements_meta_raw() as *mut Vec<Option<PropMetaEntry>>;
if !elems_meta_ptr.is_null() {
let vec = Box::from_raw(elems_meta_ptr);
freed_bytes += size_of::<Vec<Option<PropMetaEntry>>>() as u64
+ (vec.capacity() * size_of::<Option<PropMetaEntry>>()) as u64;
std::mem::drop(vec);
}
let hash_ptr = obj.hash_props_raw() as *mut Vec<JsValue>;
if !hash_ptr.is_null() {
let vec = Box::from_raw(hash_ptr);
@@ -763,6 +798,39 @@ mod tests {
.any(|ptr| unsafe { (*(*ptr)).is_gc_marked() }));
}
#[test]
fn sweep_preserves_array_elements_and_collects_dead_element_object() {
let mut vm = Vm::new();
let array_proto = vm.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
let arr = vm.epoch.alloc(JsObject::new_array(
oxide_kernel::shape_forge::EMPTY_SHAPE_ID,
JsValue::from_js_object(array_proto),
2,
vm.epoch.bump(),
));
let live_elem = plain_object(&mut vm);
let dead_elem = plain_object(&mut vm);
unsafe {
(*arr).set_prop_at(0, JsValue::from_js_object(live_elem));
(*arr).set_prop_at(1, JsValue::from_js_object(dead_elem));
}
// 仅元素 0 的对象作为 GC 根:元素 1 的对象应被回收。
let arr_session = vm.promote_object(arr);
vm.regs[0] = JsValue::from_js_object(arr_session);
vm.regs[1] = JsValue::from_js_object(live_elem);
let mut gc = std::mem::take(&mut vm.gc_state.session_gc);
gc.mark(&vm);
let _ = gc.sweep(&mut vm);
vm.gc_state.session_gc = gc;
// 存活对象经克隆 + rewrite:数组元素 0 指向晋升后的 live_elem。
let live_session = unsafe { (*arr_session).get_prop_at(0).as_js_object_ptr() };
assert_eq!(unsafe { (*live_session).prop_count() }, 0);
assert_eq!(unsafe { (*arr_session).prop_count() }, 2);
assert_eq!(unsafe { (*arr_session).get_prop_at(0).as_js_object_ptr() }, live_session);
}
#[test]
fn forwarding_is_cleared_after_sweep() {
let mut vm = Vm::new();